Initial import
See b/170209503#comment3 for reference.
Change-Id: I7a11889f731a18d9a36eaae9e6c23d2f188e9a64
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..28586d9
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,30 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.cs text
+*.ps1 text
+*.psm1 text
+*.psd1 text
+*.config text
+*.md text
+*.txt text
+*.xml text
+*.resx text
+
+.gitattributes text
+.gitignore text
+
+# Declare files that will always have CRLF line endings on checkout.
+*.sln text eol=crlf
+*.csproj text eol=crlf
+
+# Denote all files that are truly binary and should not be modified.
+*.png binary
+*.jpg binary
+*.docx binary
+*.xlsx binary
+*.pptx binary
+
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..78b1ae3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+*.exe
+*.pdb
+*.cache
+*.dll
+Thumbs.db
+
+# output files from the OpenXmlPowerTools examples
+**/DocumentAssembler/AssembledDoc.docx
+**/DocumentAssembler01/AssembledDoc.docx
+**/ChartUpdater01/Updated-Chart*
+**/FormattingAssembler01/*out.docx
+**/HtmlConverter01/*.html
+**/HtmlConverter01/*_files
+**/TextReplacer01/*out*
+**/SpreadsheetWriter01/Test1.xlsx
+**/SpreadsheetWriter02/Test2.xlsx
+**/PivotTables01/NewPivot.xlsx
+**/PivotTables01/QuarterlyPivot.xlsx
+**/PivotTables01/QuarterlyUnitSalesWithPivot.xlsx
+
+*.suo
+*.user
+*conflicted*
+*FileListAbsolute.txt
+*Settings.settings
+[Bb]in/
+[Oo]bj/
+TestResults/
+
+# NuGet Packages
+*.nupkg
+**/packages/*
+*.lock.json
+*.nuget.props
+*.nuget.targets
+
+# Visual Studio
+.vs/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2da479b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,40 @@
+Copyright (c) Microsoft Corporation
+
+This license governs use of the accompanying software. If you use the software, you
+accept this license. If you do not accept the license, do not use the software.
+
+1. Definitions
+The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
+same meaning here as under U.S. copyright law.
+A "contribution" is the original software, or any additions or changes to the software.
+A "contributor" is any person that distributes its contribution under this license.
+"Licensed patents" are a contributor's patent claims that read directly on its contribution.
+
+2. Grant of Rights
+(A) Copyright Grant- Subject to the terms of this license, including the license conditions
+and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
+royalty-free copyright license to reproduce its contribution, prepare derivative works of
+its contribution, and distribute its contribution or any derivative works that you create.
+(B) Patent Grant- Subject to the terms of this license, including the license conditions
+and limitations in section 3, each contributor grants you a non-exclusive, worldwide,
+royalty-free license under its licensed patents to make, have made, use, sell, offer for
+sale, import, and/or otherwise dispose of its contribution in the software or derivative
+works of the contribution in the software.
+
+3. Conditions and Limitations
+(A) No Trademark License- This license does not grant you rights to use any contributors'
+name, logo, or trademarks.
+(B) If you bring a patent claim against any contributor over patents that you claim are
+infringed by the software, your patent license from such contributor to the software ends
+automatically.
+(C) If you distribute any portion of the software, you must retain all copyright, patent,
+trademark, and attribution notices that are present in the software.
+(D) If you distribute any portion of the software in source code form, you may do so only
+under this license by including a complete copy of this license with your distribution.
+If you distribute any portion of the software in compiled or object code form, you may
+only do so under a license that complies with this license.
+(E) The software is licensed "as-is." You bear the risk of using it. The contributors give
+no express warranties, guarantees or conditions. You may have additional consumer rights
+under your local laws which this license cannot change. To the extent permitted under your
+local laws, the contributors exclude the implied warranties of merchantability, fitness
+for a particular purpose and non-infringement.
\ No newline at end of file
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d21aaac
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,15 @@
+name: "Open-Xml-PowerTools"
+description:
+ "The Open XML PowerTools provides guidance and example code for programming"
+ " with Open XML Documents (DOCX, XLSX, and PPTX). It is based on, and "
+ "extends the functionality of the Open XML SDK."
+
+third_party {
+ url {
+ type: GIT
+ value: "https://github.com/OfficeDev/Open-Xml-PowerTools"
+ }
+ version: a4232b90c7f04a12bf972ea9f65b2e9f3ae12448
+ last_upgrade_date { year: 2020 month: 10 day: 21 }
+ license_type: NOTICE
+}
\ No newline at end of file
diff --git a/NewDocxDocuments/Bookmark.docx b/NewDocxDocuments/Bookmark.docx
new file mode 100644
index 0000000..0652f51
--- /dev/null
+++ b/NewDocxDocuments/Bookmark.docx
Binary files differ
diff --git a/NewDocxDocuments/Bookmark.txt b/NewDocxDocuments/Bookmark.txt
new file mode 100644
index 0000000..0aaa596
--- /dev/null
+++ b/NewDocxDocuments/Bookmark.txt
@@ -0,0 +1 @@
+Bookmark and hyperlink to the bookmark.
diff --git a/NewDocxDocuments/BulletedText.docx b/NewDocxDocuments/BulletedText.docx
new file mode 100644
index 0000000..2006bea
--- /dev/null
+++ b/NewDocxDocuments/BulletedText.docx
Binary files differ
diff --git a/NewDocxDocuments/BulletedText.txt b/NewDocxDocuments/BulletedText.txt
new file mode 100644
index 0000000..206bc2a
--- /dev/null
+++ b/NewDocxDocuments/BulletedText.txt
@@ -0,0 +1 @@
+Bulleted text.
diff --git a/NewDocxDocuments/Chart.docx b/NewDocxDocuments/Chart.docx
new file mode 100644
index 0000000..df550b8
--- /dev/null
+++ b/NewDocxDocuments/Chart.docx
Binary files differ
diff --git a/NewDocxDocuments/Chart.txt b/NewDocxDocuments/Chart.txt
new file mode 100644
index 0000000..06a4726
--- /dev/null
+++ b/NewDocxDocuments/Chart.txt
@@ -0,0 +1 @@
+Chart.
diff --git a/NewDocxDocuments/Comment.docx b/NewDocxDocuments/Comment.docx
new file mode 100644
index 0000000..2485aef
--- /dev/null
+++ b/NewDocxDocuments/Comment.docx
Binary files differ
diff --git a/NewDocxDocuments/Comment.txt b/NewDocxDocuments/Comment.txt
new file mode 100644
index 0000000..979cdd4
--- /dev/null
+++ b/NewDocxDocuments/Comment.txt
@@ -0,0 +1 @@
+Paragraph that contains a comment.
diff --git a/NewDocxDocuments/ContentControls.docx b/NewDocxDocuments/ContentControls.docx
new file mode 100644
index 0000000..e0a026e
--- /dev/null
+++ b/NewDocxDocuments/ContentControls.docx
Binary files differ
diff --git a/NewDocxDocuments/ContentControls.txt b/NewDocxDocuments/ContentControls.txt
new file mode 100644
index 0000000..9021ec2
--- /dev/null
+++ b/NewDocxDocuments/ContentControls.txt
@@ -0,0 +1 @@
+Content controls of various types.
diff --git a/NewDocxDocuments/ContentControlsNested.docx b/NewDocxDocuments/ContentControlsNested.docx
new file mode 100644
index 0000000..4d00eed
--- /dev/null
+++ b/NewDocxDocuments/ContentControlsNested.docx
Binary files differ
diff --git a/NewDocxDocuments/ContentControlsNested.txt b/NewDocxDocuments/ContentControlsNested.txt
new file mode 100644
index 0000000..be2d6f0
--- /dev/null
+++ b/NewDocxDocuments/ContentControlsNested.txt
Binary files differ
diff --git a/NewDocxDocuments/CoverPage.docx b/NewDocxDocuments/CoverPage.docx
new file mode 100644
index 0000000..ed80580
--- /dev/null
+++ b/NewDocxDocuments/CoverPage.docx
Binary files differ
diff --git a/NewDocxDocuments/CoverPage.txt b/NewDocxDocuments/CoverPage.txt
new file mode 100644
index 0000000..7e8fe36
--- /dev/null
+++ b/NewDocxDocuments/CoverPage.txt
@@ -0,0 +1 @@
+Cover page.
diff --git a/NewDocxDocuments/EmbeddedWorkbook.docx b/NewDocxDocuments/EmbeddedWorkbook.docx
new file mode 100644
index 0000000..6245aef
--- /dev/null
+++ b/NewDocxDocuments/EmbeddedWorkbook.docx
Binary files differ
diff --git a/NewDocxDocuments/EmbeddedWorkbook.txt b/NewDocxDocuments/EmbeddedWorkbook.txt
new file mode 100644
index 0000000..61b55fb
--- /dev/null
+++ b/NewDocxDocuments/EmbeddedWorkbook.txt
@@ -0,0 +1 @@
+Embedded Excel workbook.
diff --git a/NewDocxDocuments/Empty.docx b/NewDocxDocuments/Empty.docx
new file mode 100644
index 0000000..0f5401a
--- /dev/null
+++ b/NewDocxDocuments/Empty.docx
Binary files differ
diff --git a/NewDocxDocuments/Empty.txt b/NewDocxDocuments/Empty.txt
new file mode 100644
index 0000000..cca1988
--- /dev/null
+++ b/NewDocxDocuments/Empty.txt
@@ -0,0 +1 @@
+Single blank paragraph.
diff --git a/NewDocxDocuments/EndNote.docx b/NewDocxDocuments/EndNote.docx
new file mode 100644
index 0000000..33804c1
--- /dev/null
+++ b/NewDocxDocuments/EndNote.docx
Binary files differ
diff --git a/NewDocxDocuments/EndNote.txt b/NewDocxDocuments/EndNote.txt
new file mode 100644
index 0000000..c41eb0d
--- /dev/null
+++ b/NewDocxDocuments/EndNote.txt
@@ -0,0 +1 @@
+Endnote.
diff --git a/NewDocxDocuments/Equation.docx b/NewDocxDocuments/Equation.docx
new file mode 100644
index 0000000..2256af9
--- /dev/null
+++ b/NewDocxDocuments/Equation.docx
Binary files differ
diff --git a/NewDocxDocuments/Equation.txt b/NewDocxDocuments/Equation.txt
new file mode 100644
index 0000000..55fdc23
--- /dev/null
+++ b/NewDocxDocuments/Equation.txt
@@ -0,0 +1 @@
+Math equation.
diff --git a/NewDocxDocuments/Fields.docx b/NewDocxDocuments/Fields.docx
new file mode 100644
index 0000000..d91a9bf
--- /dev/null
+++ b/NewDocxDocuments/Fields.docx
Binary files differ
diff --git a/NewDocxDocuments/Fields.txt b/NewDocxDocuments/Fields.txt
new file mode 100644
index 0000000..64983c8
--- /dev/null
+++ b/NewDocxDocuments/Fields.txt
@@ -0,0 +1 @@
+Date-time field
diff --git a/NewDocxDocuments/Fonts.docx b/NewDocxDocuments/Fonts.docx
new file mode 100644
index 0000000..2a17822
--- /dev/null
+++ b/NewDocxDocuments/Fonts.docx
Binary files differ
diff --git a/NewDocxDocuments/Fonts.txt b/NewDocxDocuments/Fonts.txt
new file mode 100644
index 0000000..18758b4
--- /dev/null
+++ b/NewDocxDocuments/Fonts.txt
@@ -0,0 +1 @@
+Various fonts.
diff --git a/NewDocxDocuments/FootNote.docx b/NewDocxDocuments/FootNote.docx
new file mode 100644
index 0000000..b6bffdd
--- /dev/null
+++ b/NewDocxDocuments/FootNote.docx
Binary files differ
diff --git a/NewDocxDocuments/FootNote.txt b/NewDocxDocuments/FootNote.txt
new file mode 100644
index 0000000..178f636
--- /dev/null
+++ b/NewDocxDocuments/FootNote.txt
@@ -0,0 +1 @@
+Footnote.
diff --git a/NewDocxDocuments/FormattedText.docx b/NewDocxDocuments/FormattedText.docx
new file mode 100644
index 0000000..9a89eba
--- /dev/null
+++ b/NewDocxDocuments/FormattedText.docx
Binary files differ
diff --git a/NewDocxDocuments/FormattedText.txt b/NewDocxDocuments/FormattedText.txt
new file mode 100644
index 0000000..f0bd1d9
--- /dev/null
+++ b/NewDocxDocuments/FormattedText.txt
@@ -0,0 +1 @@
+Two paragraphs with text formatting.
diff --git a/NewDocxDocuments/GenerateNewDocxCmdlet.ps1 b/NewDocxDocuments/GenerateNewDocxCmdlet.ps1
new file mode 100644
index 0000000..b3b04b1
--- /dev/null
+++ b/NewDocxDocuments/GenerateNewDocxCmdlet.ps1
@@ -0,0 +1,107 @@
+[environment]::CurrentDirectory = $(Get-Location)
+if (-not $(Test-Path .\GenerateNewDocxCmdlet.ps1))
+{
+ Throw "You must run this script from within the NewDocxDocuments directory"
+}
+
+$dx = "..\Cmdlets\DocxLib.ps1"
+if (Test-Path $dx) { del $dx }
+
+$lineBreak = [System.Environment]::NewLine
+
+[System.Text.StringBuilder]$sbDxl = New-Object -TypeName System.Text.StringBuilder
+
+$copyrightString = @"
+<#***************************************************************************
+
+Copyright (c) Microsoft Corporation 2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+***************************************************************************#>
+
+"@
+
+[void]$sbDxl.Append($copyrightString + $lineBreak)
+
+dir *.docx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbDxl.Append("`$SampleDocx$($_.BaseName) =" + $lineBreak)
+ $b64 = $(ConvertTo-Base64 $_ -PowerShellLiteral)
+ [void]$sbDxl.Append($b64 + $lineBreak)
+ [void]$sbDxl.Append("" + $lineBreak)
+}
+Set-Content -Value $sbDxl.ToString() -Path $dx -Encoding UTF8
+
+$template = [System.IO.File]::ReadAllLines("..\Cmdlets\New-Docx-Template.ps1")
+$paramDocs = -1;
+$paramDecl = -1;
+$paramUse = -1;
+for ($i = 0; $i -lt $template.Length; $i++)
+{
+ $t = $template[$i]
+ if ($t.Contains("ParameterDocumentation")) { $paramDocs = $i }
+ if ($t.Contains("ParameterDeclaration")) { $paramDecl = $i }
+ if ($t.Contains("ParameterUse")) { $paramUse = $i }
+}
+
+$ndx = "..\Cmdlets\New-Docx.ps1"
+if (Test-Path $ndx)
+{
+ Remove-Item $ndx
+}
+
+$sbGenNewDocx = New-Object System.Text.StringBuilder;
+
+$template[0..($paramDocs - 1)] | % { [void]$sbGenNewDocx.Append($_ + $lineBreak) }
+dir *.docx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbGenNewDocx.Append(" .PARAMETER $($fi.BaseName)" + $lineBreak)
+ $fiDesc = New-Object System.IO.FileInfo $($_.BaseName + ".txt")
+ if ($fiDesc.Exists)
+ {
+ Get-Content $($fiDesc.FullName) | % { [void]$sbGenNewDocx.Append(' ' + $_ + $lineBreak) }
+ }
+ else
+ {
+ $errMessage = "Error: $($fi.BaseName).docx does not have a corresponding $($fi.BaseName).txt"
+ Write-Host -ForegroundColor Red $errMessage
+ [void]$sbGenNewDocx.Append(' ' + $errMessage + $lineBreak)
+ }
+}
+$start = $paramDocs + 1
+$end = $paramDecl - 1
+$template[$start..$end] | % { [void]$sbGenNewDocx.Append($_ + $lineBreak) }
+$last = (($(dir *.docx) | measure).Count) - 1
+$count = 0
+dir *.docx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbGenNewDocx.Append(' [Parameter(Mandatory=$False)]' + $lineBreak)
+ [void]$sbGenNewDocx.Append(' [Switch]' + $lineBreak)
+ if ($count -ne $last)
+ {
+ [void]$sbGenNewDocx.Append(" [bool]`$$($_.BaseName)," + $lineBreak)
+ }
+ else
+ {
+ [void]$sbGenNewDocx.Append(" [bool]`$$($_.BaseName)" + $lineBreak)
+ }
+ [void]$sbGenNewDocx.Append($lineBreak)
+ $count++
+}
+$start = $paramDecl + 1
+$end = $paramUse - 1
+$template[$start..$end] | % { [void]$sbGenNewDocx.Append($_ + $lineBreak) }
+dir *.docx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbGenNewDocx.Append(" if (`$All -or `$$($fi.BaseName)) { AppendDoc `$srcList `$SampleDocx$($fi.BaseName) `"$($fi.BaseName)`" }" + $lineBreak)
+}
+$start = $paramUse + 1
+$template[$start..99999] | % { [void]$sbGenNewDocx.Append($_ + $lineBreak) }
+
+Set-Content -Value $sbGenNewDocx.ToString() -Path $ndx -Encoding UTF8
diff --git a/NewDocxDocuments/Headings.docx b/NewDocxDocuments/Headings.docx
new file mode 100644
index 0000000..4655e3f
--- /dev/null
+++ b/NewDocxDocuments/Headings.docx
Binary files differ
diff --git a/NewDocxDocuments/Headings.txt b/NewDocxDocuments/Headings.txt
new file mode 100644
index 0000000..4a2e7ac
--- /dev/null
+++ b/NewDocxDocuments/Headings.txt
@@ -0,0 +1 @@
+Headings
diff --git a/NewDocxDocuments/HierarchicalNumberedList.docx b/NewDocxDocuments/HierarchicalNumberedList.docx
new file mode 100644
index 0000000..139a8ef
--- /dev/null
+++ b/NewDocxDocuments/HierarchicalNumberedList.docx
Binary files differ
diff --git a/NewDocxDocuments/HierarchicalNumberedList.txt b/NewDocxDocuments/HierarchicalNumberedList.txt
new file mode 100644
index 0000000..07203f9
--- /dev/null
+++ b/NewDocxDocuments/HierarchicalNumberedList.txt
@@ -0,0 +1 @@
+Hierarchical numbered list.
diff --git a/NewDocxDocuments/HorizontalWhiteSpace.docx b/NewDocxDocuments/HorizontalWhiteSpace.docx
new file mode 100644
index 0000000..64748d3
--- /dev/null
+++ b/NewDocxDocuments/HorizontalWhiteSpace.docx
Binary files differ
diff --git a/NewDocxDocuments/HorizontalWhiteSpace.txt b/NewDocxDocuments/HorizontalWhiteSpace.txt
new file mode 100644
index 0000000..a6fef1f
--- /dev/null
+++ b/NewDocxDocuments/HorizontalWhiteSpace.txt
@@ -0,0 +1 @@
+First line indent, hanging indent, indented paragraphs.
diff --git a/NewDocxDocuments/Hyperlink.docx b/NewDocxDocuments/Hyperlink.docx
new file mode 100644
index 0000000..892b9e2
--- /dev/null
+++ b/NewDocxDocuments/Hyperlink.docx
Binary files differ
diff --git a/NewDocxDocuments/Hyperlink.txt b/NewDocxDocuments/Hyperlink.txt
new file mode 100644
index 0000000..0aaa596
--- /dev/null
+++ b/NewDocxDocuments/Hyperlink.txt
@@ -0,0 +1 @@
+Bookmark and hyperlink to the bookmark.
diff --git a/NewDocxDocuments/Image.docx b/NewDocxDocuments/Image.docx
new file mode 100644
index 0000000..cf656fb
--- /dev/null
+++ b/NewDocxDocuments/Image.docx
Binary files differ
diff --git a/NewDocxDocuments/Image.txt b/NewDocxDocuments/Image.txt
new file mode 100644
index 0000000..97796a0
--- /dev/null
+++ b/NewDocxDocuments/Image.txt
@@ -0,0 +1 @@
+Image.
diff --git a/NewDocxDocuments/Justified.docx b/NewDocxDocuments/Justified.docx
new file mode 100644
index 0000000..0b38b7d
--- /dev/null
+++ b/NewDocxDocuments/Justified.docx
Binary files differ
diff --git a/NewDocxDocuments/Justified.txt b/NewDocxDocuments/Justified.txt
new file mode 100644
index 0000000..4362353
--- /dev/null
+++ b/NewDocxDocuments/Justified.txt
@@ -0,0 +1 @@
+Justified text.
diff --git a/NewDocxDocuments/NumberedList.docx b/NewDocxDocuments/NumberedList.docx
new file mode 100644
index 0000000..42fcf1a
--- /dev/null
+++ b/NewDocxDocuments/NumberedList.docx
Binary files differ
diff --git a/NewDocxDocuments/NumberedList.txt b/NewDocxDocuments/NumberedList.txt
new file mode 100644
index 0000000..1885b57
--- /dev/null
+++ b/NewDocxDocuments/NumberedList.txt
@@ -0,0 +1 @@
+Simple numbered list.
diff --git a/NewDocxDocuments/NumberedListRomanNumerals.docx b/NewDocxDocuments/NumberedListRomanNumerals.docx
new file mode 100644
index 0000000..07708f5
--- /dev/null
+++ b/NewDocxDocuments/NumberedListRomanNumerals.docx
Binary files differ
diff --git a/NewDocxDocuments/NumberedListRomanNumerals.txt b/NewDocxDocuments/NumberedListRomanNumerals.txt
new file mode 100644
index 0000000..c53c854
--- /dev/null
+++ b/NewDocxDocuments/NumberedListRomanNumerals.txt
Binary files differ
diff --git a/NewDocxDocuments/ParagraphBorder.docx b/NewDocxDocuments/ParagraphBorder.docx
new file mode 100644
index 0000000..b1c7db9
--- /dev/null
+++ b/NewDocxDocuments/ParagraphBorder.docx
Binary files differ
diff --git a/NewDocxDocuments/ParagraphBorder.txt b/NewDocxDocuments/ParagraphBorder.txt
new file mode 100644
index 0000000..0936405
--- /dev/null
+++ b/NewDocxDocuments/ParagraphBorder.txt
@@ -0,0 +1 @@
+Paragraph border.
diff --git a/NewDocxDocuments/Plain.docx b/NewDocxDocuments/Plain.docx
new file mode 100644
index 0000000..3953ff8
--- /dev/null
+++ b/NewDocxDocuments/Plain.docx
Binary files differ
diff --git a/NewDocxDocuments/Plain.txt b/NewDocxDocuments/Plain.txt
new file mode 100644
index 0000000..d49c129
--- /dev/null
+++ b/NewDocxDocuments/Plain.txt
@@ -0,0 +1 @@
+Plain text.
diff --git a/NewDocxDocuments/RevisionTracking.docx b/NewDocxDocuments/RevisionTracking.docx
new file mode 100644
index 0000000..503e0c0
--- /dev/null
+++ b/NewDocxDocuments/RevisionTracking.docx
Binary files differ
diff --git a/NewDocxDocuments/RevisionTracking.txt b/NewDocxDocuments/RevisionTracking.txt
new file mode 100644
index 0000000..962717a
--- /dev/null
+++ b/NewDocxDocuments/RevisionTracking.txt
@@ -0,0 +1 @@
+Revision tracking.
diff --git a/NewDocxDocuments/RightJustified.docx b/NewDocxDocuments/RightJustified.docx
new file mode 100644
index 0000000..1f5e0aa
--- /dev/null
+++ b/NewDocxDocuments/RightJustified.docx
Binary files differ
diff --git a/NewDocxDocuments/RightJustified.txt b/NewDocxDocuments/RightJustified.txt
new file mode 100644
index 0000000..5128c3d
--- /dev/null
+++ b/NewDocxDocuments/RightJustified.txt
@@ -0,0 +1 @@
+Right justified text.
diff --git a/NewDocxDocuments/Section.docx b/NewDocxDocuments/Section.docx
new file mode 100644
index 0000000..c44b2f8
--- /dev/null
+++ b/NewDocxDocuments/Section.docx
Binary files differ
diff --git a/NewDocxDocuments/Section.txt b/NewDocxDocuments/Section.txt
new file mode 100644
index 0000000..ea4460e
--- /dev/null
+++ b/NewDocxDocuments/Section.txt
@@ -0,0 +1 @@
+Landscape section.
diff --git a/NewDocxDocuments/SectionWithWatermark.docx b/NewDocxDocuments/SectionWithWatermark.docx
new file mode 100644
index 0000000..6dc9727
--- /dev/null
+++ b/NewDocxDocuments/SectionWithWatermark.docx
Binary files differ
diff --git a/NewDocxDocuments/SectionWithWatermark.txt b/NewDocxDocuments/SectionWithWatermark.txt
new file mode 100644
index 0000000..0301e97
--- /dev/null
+++ b/NewDocxDocuments/SectionWithWatermark.txt
@@ -0,0 +1 @@
+Section with watermark.
diff --git a/NewDocxDocuments/Shading.docx b/NewDocxDocuments/Shading.docx
new file mode 100644
index 0000000..de98f0a
--- /dev/null
+++ b/NewDocxDocuments/Shading.docx
Binary files differ
diff --git a/NewDocxDocuments/Shading.txt b/NewDocxDocuments/Shading.txt
new file mode 100644
index 0000000..bcf31ae
--- /dev/null
+++ b/NewDocxDocuments/Shading.txt
@@ -0,0 +1 @@
+Shaded paragraph.
diff --git a/NewDocxDocuments/Shape.docx b/NewDocxDocuments/Shape.docx
new file mode 100644
index 0000000..d940cba
--- /dev/null
+++ b/NewDocxDocuments/Shape.docx
Binary files differ
diff --git a/NewDocxDocuments/Shape.txt b/NewDocxDocuments/Shape.txt
new file mode 100644
index 0000000..ec1c2b5
--- /dev/null
+++ b/NewDocxDocuments/Shape.txt
@@ -0,0 +1 @@
+Shapes.
diff --git a/NewDocxDocuments/SmartArt.docx b/NewDocxDocuments/SmartArt.docx
new file mode 100644
index 0000000..8715b30
--- /dev/null
+++ b/NewDocxDocuments/SmartArt.docx
Binary files differ
diff --git a/NewDocxDocuments/SmartArt.txt b/NewDocxDocuments/SmartArt.txt
new file mode 100644
index 0000000..c3657a0
--- /dev/null
+++ b/NewDocxDocuments/SmartArt.txt
@@ -0,0 +1 @@
+SmartArt.
diff --git a/NewDocxDocuments/Symbols.docx b/NewDocxDocuments/Symbols.docx
new file mode 100644
index 0000000..5bf81ce
--- /dev/null
+++ b/NewDocxDocuments/Symbols.docx
Binary files differ
diff --git a/NewDocxDocuments/Symbols.txt b/NewDocxDocuments/Symbols.txt
new file mode 100644
index 0000000..01e1601
--- /dev/null
+++ b/NewDocxDocuments/Symbols.txt
@@ -0,0 +1 @@
+Symbols.
diff --git a/NewDocxDocuments/Table.docx b/NewDocxDocuments/Table.docx
new file mode 100644
index 0000000..f89cc9a
--- /dev/null
+++ b/NewDocxDocuments/Table.docx
Binary files differ
diff --git a/NewDocxDocuments/Table.txt b/NewDocxDocuments/Table.txt
new file mode 100644
index 0000000..e83f8ec
--- /dev/null
+++ b/NewDocxDocuments/Table.txt
@@ -0,0 +1 @@
+Table that has a table style applied.
diff --git a/NewDocxDocuments/TableOfContents.docx b/NewDocxDocuments/TableOfContents.docx
new file mode 100644
index 0000000..40efe90
--- /dev/null
+++ b/NewDocxDocuments/TableOfContents.docx
Binary files differ
diff --git a/NewDocxDocuments/TableOfContents.txt b/NewDocxDocuments/TableOfContents.txt
new file mode 100644
index 0000000..c5022a9
--- /dev/null
+++ b/NewDocxDocuments/TableOfContents.txt
@@ -0,0 +1 @@
+Table of contents.
diff --git a/NewDocxDocuments/TextBox.docx b/NewDocxDocuments/TextBox.docx
new file mode 100644
index 0000000..7e2a9a4
--- /dev/null
+++ b/NewDocxDocuments/TextBox.docx
Binary files differ
diff --git a/NewDocxDocuments/TextBox.txt b/NewDocxDocuments/TextBox.txt
new file mode 100644
index 0000000..d0d6ddf
--- /dev/null
+++ b/NewDocxDocuments/TextBox.txt
@@ -0,0 +1 @@
+Text box.
diff --git a/NewDocxDocuments/TextEffects.docx b/NewDocxDocuments/TextEffects.docx
new file mode 100644
index 0000000..ce234eb
--- /dev/null
+++ b/NewDocxDocuments/TextEffects.docx
Binary files differ
diff --git a/NewDocxDocuments/TextEffects.txt b/NewDocxDocuments/TextEffects.txt
new file mode 100644
index 0000000..5ec3854
--- /dev/null
+++ b/NewDocxDocuments/TextEffects.txt
@@ -0,0 +1 @@
+Text effects.
diff --git a/NewDocxDocuments/Theme.docx b/NewDocxDocuments/Theme.docx
new file mode 100644
index 0000000..2b5e7d2
--- /dev/null
+++ b/NewDocxDocuments/Theme.docx
Binary files differ
diff --git a/NewDocxDocuments/Theme.txt b/NewDocxDocuments/Theme.txt
new file mode 100644
index 0000000..4ac57aa
--- /dev/null
+++ b/NewDocxDocuments/Theme.txt
@@ -0,0 +1 @@
+Theme.
diff --git a/NewDocxDocuments/VerticalWhiteSpace.docx b/NewDocxDocuments/VerticalWhiteSpace.docx
new file mode 100644
index 0000000..6154742
--- /dev/null
+++ b/NewDocxDocuments/VerticalWhiteSpace.docx
Binary files differ
diff --git a/NewDocxDocuments/VerticalWhiteSpace.txt b/NewDocxDocuments/VerticalWhiteSpace.txt
new file mode 100644
index 0000000..cbcbdc8
--- /dev/null
+++ b/NewDocxDocuments/VerticalWhiteSpace.txt
@@ -0,0 +1 @@
+Vertical space before and after paragraphs.
diff --git a/NewPptxPresentations/AdjacencyTheme.pptx b/NewPptxPresentations/AdjacencyTheme.pptx
new file mode 100644
index 0000000..61798ab
--- /dev/null
+++ b/NewPptxPresentations/AdjacencyTheme.pptx
Binary files differ
diff --git a/NewPptxPresentations/AdjacencyTheme.txt b/NewPptxPresentations/AdjacencyTheme.txt
new file mode 100644
index 0000000..59b5139
--- /dev/null
+++ b/NewPptxPresentations/AdjacencyTheme.txt
@@ -0,0 +1 @@
+Adjacency Theme
diff --git a/NewPptxPresentations/AnglesTheme.pptx b/NewPptxPresentations/AnglesTheme.pptx
new file mode 100644
index 0000000..6e83bdb
--- /dev/null
+++ b/NewPptxPresentations/AnglesTheme.pptx
Binary files differ
diff --git a/NewPptxPresentations/AnglesTheme.txt b/NewPptxPresentations/AnglesTheme.txt
new file mode 100644
index 0000000..49f8015
--- /dev/null
+++ b/NewPptxPresentations/AnglesTheme.txt
@@ -0,0 +1 @@
+Angles Theme.
diff --git a/NewPptxPresentations/ApexTheme.pptx b/NewPptxPresentations/ApexTheme.pptx
new file mode 100644
index 0000000..7753c45
--- /dev/null
+++ b/NewPptxPresentations/ApexTheme.pptx
Binary files differ
diff --git a/NewPptxPresentations/ApexTheme.txt b/NewPptxPresentations/ApexTheme.txt
new file mode 100644
index 0000000..09e4b4c
--- /dev/null
+++ b/NewPptxPresentations/ApexTheme.txt
@@ -0,0 +1 @@
+Apex Theme.
diff --git a/NewPptxPresentations/BlankLayout.pptx b/NewPptxPresentations/BlankLayout.pptx
new file mode 100644
index 0000000..107633b
--- /dev/null
+++ b/NewPptxPresentations/BlankLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/BlankLayout.txt b/NewPptxPresentations/BlankLayout.txt
new file mode 100644
index 0000000..a166756
--- /dev/null
+++ b/NewPptxPresentations/BlankLayout.txt
@@ -0,0 +1 @@
+Slide with Blank layout.
diff --git a/NewPptxPresentations/ComparisonLayout.pptx b/NewPptxPresentations/ComparisonLayout.pptx
new file mode 100644
index 0000000..0c49452
--- /dev/null
+++ b/NewPptxPresentations/ComparisonLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/ComparisonLayout.txt b/NewPptxPresentations/ComparisonLayout.txt
new file mode 100644
index 0000000..a3181af
--- /dev/null
+++ b/NewPptxPresentations/ComparisonLayout.txt
@@ -0,0 +1 @@
+Slide with Comparison layout.
diff --git a/NewPptxPresentations/ContentWithCaptionLayout.pptx b/NewPptxPresentations/ContentWithCaptionLayout.pptx
new file mode 100644
index 0000000..c5c5c5e
--- /dev/null
+++ b/NewPptxPresentations/ContentWithCaptionLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/ContentWithCaptionLayout.txt b/NewPptxPresentations/ContentWithCaptionLayout.txt
new file mode 100644
index 0000000..b367fcd
--- /dev/null
+++ b/NewPptxPresentations/ContentWithCaptionLayout.txt
@@ -0,0 +1 @@
+Slide with Content With Caption layout.
diff --git a/NewPptxPresentations/Empty.pptx b/NewPptxPresentations/Empty.pptx
new file mode 100644
index 0000000..f6b42d0
--- /dev/null
+++ b/NewPptxPresentations/Empty.pptx
Binary files differ
diff --git a/NewPptxPresentations/Empty.txt b/NewPptxPresentations/Empty.txt
new file mode 100644
index 0000000..0f408ac
--- /dev/null
+++ b/NewPptxPresentations/Empty.txt
@@ -0,0 +1 @@
+No slides.
diff --git a/NewPptxPresentations/FiveSlides.pptx b/NewPptxPresentations/FiveSlides.pptx
new file mode 100644
index 0000000..c507324
--- /dev/null
+++ b/NewPptxPresentations/FiveSlides.pptx
Binary files differ
diff --git a/NewPptxPresentations/FiveSlides.txt b/NewPptxPresentations/FiveSlides.txt
new file mode 100644
index 0000000..0f408ac
--- /dev/null
+++ b/NewPptxPresentations/FiveSlides.txt
@@ -0,0 +1 @@
+No slides.
diff --git a/NewPptxPresentations/GenerateNewPptxCmdlet.ps1 b/NewPptxPresentations/GenerateNewPptxCmdlet.ps1
new file mode 100644
index 0000000..2ceb732
--- /dev/null
+++ b/NewPptxPresentations/GenerateNewPptxCmdlet.ps1
@@ -0,0 +1,107 @@
+[environment]::CurrentDirectory = $(Get-Location)
+if (-not $(Test-Path .\GenerateNewPptxCmdlet.ps1))
+{
+ Throw "You must run this script from within the NewPptxPresentations directory"
+}
+
+$dx = "..\Cmdlets\PptxLib.ps1"
+if (Test-Path $dx) { del $dx}
+
+$lineBreak = [System.Environment]::NewLine
+
+[System.Text.StringBuilder]$sbDxl = New-Object -TypeName System.Text.StringBuilder
+
+$copyrightString = @"
+<#***************************************************************************
+
+Copyright (c) Microsoft Corporation 2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+***************************************************************************#>
+
+"@
+
+[void]$sbDxl.Append($copyrightString + $lineBreak)
+
+dir *.pptx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbDxl.Append("`$SamplePptx$($_.BaseName) =" + $lineBreak)
+ $b64 = $(ConvertTo-Base64 $_ -PowerShellLiteral)
+ [void]$sbDxl.Append($b64 + $lineBreak)
+ [void]$sbDxl.Append("" + $lineBreak)
+}
+Set-Content -Value $sbDxl.ToString() -Path $dx -Encoding UTF8
+
+$template = [System.IO.File]::ReadAllLines("..\Cmdlets\New-Pptx-Template.ps1")
+$paramDocs = -1;
+$paramDecl = -1;
+$paramUse = -1;
+for ($i = 0; $i -lt $template.Length; $i++)
+{
+ $t = $template[$i]
+ if ($t.Contains("ParameterDocumentation")) { $paramDocs = $i }
+ if ($t.Contains("ParameterDeclaration")) { $paramDecl = $i }
+ if ($t.Contains("ParameterUse")) { $paramUse = $i }
+}
+
+$npx = "..\Cmdlets\New-Pptx.ps1"
+if (Test-Path $npx)
+{
+ Remove-Item $npx
+}
+
+$sbGenNewPptx = New-Object System.Text.StringBuilder;
+
+$template[0..($paramDocs - 1)] | % { [void]$sbGenNewPptx.Append($_ + $lineBreak) }
+dir *.pptx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbGenNewPptx.Append(" .PARAMETER $($fi.BaseName)" + $lineBreak)
+ $fiDesc = New-Object System.IO.FileInfo $($_.BaseName + ".txt")
+ if ($fiDesc.Exists)
+ {
+ Get-Content $($fiDesc.FullName) | % { [void]$sbGenNewPptx.Append(' ' + $_ + $lineBreak) }
+ }
+ else
+ {
+ $errMessage = "Error: $($fi.BaseName).pptx does not have a corresponding $($fi.BaseName).txt"
+ Write-Host -ForegroundColor Red $errMessage
+ [void]$sbGenNewPptx.Append(' ' + $errMessage + $lineBreak)
+ }
+}
+$start = $paramDocs + 1
+$end = $paramDecl - 1
+$template[$start..$end] | % { [void]$sbGenNewPptx.Append($_ + $lineBreak) }
+$last = (($(dir *.pptx) | measure).Count) - 1
+$count = 0
+dir *.pptx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbGenNewPptx.Append(' [Parameter(Mandatory=$False)]' + $lineBreak)
+ [void]$sbGenNewPptx.Append(' [Switch]' + $lineBreak)
+ if ($count -ne $last)
+ {
+ [void]$sbGenNewPptx.Append(" [bool]`$$($_.BaseName)," + $lineBreak)
+ }
+ else
+ {
+ [void]$sbGenNewPptx.Append(" [bool]`$$($_.BaseName)" + $lineBreak)
+ }
+ [void]$sbGenNewPptx.Append($lineBreak)
+ $count++
+}
+$start = $paramDecl + 1
+$end = $paramUse - 1
+$template[$start..$end] | % { [void]$sbGenNewPptx.Append($_ + $lineBreak) }
+dir *.pptx | % {
+ $fi = New-Object System.IO.FileInfo $_
+ [void]$sbGenNewPptx.Append(" if (`$All -or `$$($fi.BaseName)) { AppendPresentation `$srcList `$SamplePptx$($fi.BaseName) `"$($fi.BaseName)`" }" + $lineBreak)
+}
+$start = $paramUse + 1
+$template[$start..99999] | % { [void]$sbGenNewPptx.Append($_ + $lineBreak) }
+
+Set-Content -Value $sbGenNewPptx.ToString() -Path $npx -Encoding UTF8
diff --git a/NewPptxPresentations/PictureWithCaptionLayout.pptx b/NewPptxPresentations/PictureWithCaptionLayout.pptx
new file mode 100644
index 0000000..d8f7f86
--- /dev/null
+++ b/NewPptxPresentations/PictureWithCaptionLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/PictureWithCaptionLayout.txt b/NewPptxPresentations/PictureWithCaptionLayout.txt
new file mode 100644
index 0000000..2d13c1a
--- /dev/null
+++ b/NewPptxPresentations/PictureWithCaptionLayout.txt
@@ -0,0 +1 @@
+Slide with Picture With Caption layout.
diff --git a/NewPptxPresentations/SectionHeaderLayout.pptx b/NewPptxPresentations/SectionHeaderLayout.pptx
new file mode 100644
index 0000000..73886b0
--- /dev/null
+++ b/NewPptxPresentations/SectionHeaderLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/SectionHeaderLayout.txt b/NewPptxPresentations/SectionHeaderLayout.txt
new file mode 100644
index 0000000..d4221a6
--- /dev/null
+++ b/NewPptxPresentations/SectionHeaderLayout.txt
@@ -0,0 +1 @@
+Slide with Section Header layout.
diff --git a/NewPptxPresentations/SlideTransitions.pptx b/NewPptxPresentations/SlideTransitions.pptx
new file mode 100644
index 0000000..019330e
--- /dev/null
+++ b/NewPptxPresentations/SlideTransitions.pptx
Binary files differ
diff --git a/NewPptxPresentations/SlideTransitions.txt b/NewPptxPresentations/SlideTransitions.txt
new file mode 100644
index 0000000..8ed5338
--- /dev/null
+++ b/NewPptxPresentations/SlideTransitions.txt
@@ -0,0 +1 @@
+Slide Transitions.
diff --git a/NewPptxPresentations/TenSlides.pptx b/NewPptxPresentations/TenSlides.pptx
new file mode 100644
index 0000000..ee193b0
--- /dev/null
+++ b/NewPptxPresentations/TenSlides.pptx
Binary files differ
diff --git a/NewPptxPresentations/TenSlides.txt b/NewPptxPresentations/TenSlides.txt
new file mode 100644
index 0000000..b0e6c4f
--- /dev/null
+++ b/NewPptxPresentations/TenSlides.txt
@@ -0,0 +1 @@
+Ten Slides.
diff --git a/NewPptxPresentations/TitleAndContentLayout.pptx b/NewPptxPresentations/TitleAndContentLayout.pptx
new file mode 100644
index 0000000..72cd811
--- /dev/null
+++ b/NewPptxPresentations/TitleAndContentLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/TitleAndContentLayout.txt b/NewPptxPresentations/TitleAndContentLayout.txt
new file mode 100644
index 0000000..fd28e84
--- /dev/null
+++ b/NewPptxPresentations/TitleAndContentLayout.txt
@@ -0,0 +1 @@
+Slide with Title and Content layout.
diff --git a/NewPptxPresentations/TitleLayout.pptx b/NewPptxPresentations/TitleLayout.pptx
new file mode 100644
index 0000000..36635c7
--- /dev/null
+++ b/NewPptxPresentations/TitleLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/TitleLayout.txt b/NewPptxPresentations/TitleLayout.txt
new file mode 100644
index 0000000..3ea1fd0
--- /dev/null
+++ b/NewPptxPresentations/TitleLayout.txt
@@ -0,0 +1 @@
+Slide with Title layout.
diff --git a/NewPptxPresentations/TitleOnlyLayout.pptx b/NewPptxPresentations/TitleOnlyLayout.pptx
new file mode 100644
index 0000000..58270df
--- /dev/null
+++ b/NewPptxPresentations/TitleOnlyLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/TitleOnlyLayout.txt b/NewPptxPresentations/TitleOnlyLayout.txt
new file mode 100644
index 0000000..991fa83
--- /dev/null
+++ b/NewPptxPresentations/TitleOnlyLayout.txt
@@ -0,0 +1 @@
+Slide with Title Only layout.
diff --git a/NewPptxPresentations/TwoContentLayout.pptx b/NewPptxPresentations/TwoContentLayout.pptx
new file mode 100644
index 0000000..bb43407
--- /dev/null
+++ b/NewPptxPresentations/TwoContentLayout.pptx
Binary files differ
diff --git a/NewPptxPresentations/TwoContentLayout.txt b/NewPptxPresentations/TwoContentLayout.txt
new file mode 100644
index 0000000..966781b
--- /dev/null
+++ b/NewPptxPresentations/TwoContentLayout.txt
@@ -0,0 +1 @@
+Slide with Two Content layout.
diff --git a/NewPptxPresentations/WaveFormTheme.pptx b/NewPptxPresentations/WaveFormTheme.pptx
new file mode 100644
index 0000000..bf9bde5
--- /dev/null
+++ b/NewPptxPresentations/WaveFormTheme.pptx
Binary files differ
diff --git a/NewPptxPresentations/WaveFormTheme.txt b/NewPptxPresentations/WaveFormTheme.txt
new file mode 100644
index 0000000..4af33ac
--- /dev/null
+++ b/NewPptxPresentations/WaveFormTheme.txt
@@ -0,0 +1 @@
+Wave Form Theme.
diff --git a/OpenXmlPowerTools.Tests.OA/HtmlToWmlConverterTests2.cs b/OpenXmlPowerTools.Tests.OA/HtmlToWmlConverterTests2.cs
new file mode 100644
index 0000000..9ebce63
--- /dev/null
+++ b/OpenXmlPowerTools.Tests.OA/HtmlToWmlConverterTests2.cs
@@ -0,0 +1,558 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+using HtmlAgilityPack;
+using System.Text.RegularExpressions;
+
+/*******************************************************************************************
+ * HtmlToWmlConverter expects the HTML to be passed as an XElement, i.e. as XML. While the HTML test files that
+ * are included in Open-Xml-PowerTools are able to be read as XML, most HTML is not able to be read as XML.
+ * The best solution is to use the HtmlAgilityPack, which can parse HTML and save as XML. The HtmlAgilityPack
+ * is licensed under the Ms-PL (same as Open-Xml-PowerTools) so it is convenient to include it in your solution,
+ * and thereby you can convert HTML to XML that can be processed by the HtmlToWmlConverter.
+ *
+ * A convenient way to get the DLL that has been checked out with HtmlToWmlConverter is to clone the repo at
+ * https://github.com/EricWhiteDev/HtmlAgilityPack
+ *
+ * That repo contains only the DLL that has been checked out with HtmlToWmlConverter.
+ *
+ * Of course, you can also get the HtmlAgilityPack source and compile it to get the DLL. You can find it at
+ * http://codeplex.com/HtmlAgilityPack
+ *
+ * We don't include the HtmlAgilityPack in Open-Xml-PowerTools, to simplify installation. The XUnit tests in
+ * this module do not require the HtmlAgilityPack to run.
+*******************************************************************************************/
+
+#if DO_CONVERSION_VIA_WORD
+using Word = Microsoft.Office.Interop.Word;
+#endif
+
+/***************************************************************************************************
+ * The XUnit tests in this module are not included in the standard Open-Xml-PowerTools tests because
+ * they use either Word automation, the HtmlAgilityPack, or both.
+***************************************************************************************************/
+
+namespace OxPt
+{
+ public class HwTests2
+ {
+ static bool s_ProduceAnnotatedHtml = true;
+
+ // PowerShell oneliner that generates InlineData for all files in a directory
+ // dir | % { '[InlineData("' + $_.Name + '")]' } | clip
+
+ [Theory]
+ [InlineData("HW002-Table01.docx")]
+ [InlineData("HW002-Table02.docx")]
+ [InlineData("HW002-Table03.docx")]
+ [InlineData("HW002-Table04.docx")]
+ [InlineData("HW002-Table05.docx")]
+ [InlineData("HW002-Table06.docx")]
+ [InlineData("HW002-Table07.docx")]
+ [InlineData("HW002-Table08.docx")]
+ [InlineData("HW002-Table09.docx")]
+ [InlineData("HW002-Table10.docx")]
+ [InlineData("HW002-Table11.docx")]
+ [InlineData("HW002-Table12.docx")]
+ [InlineData("HW002-Table13.docx")]
+ [InlineData("HW002-Table14.docx")]
+ [InlineData("HW002-Table15.docx")]
+ [InlineData("HW002-Table16.docx")]
+ [InlineData("HW002-Table17.docx")]
+ [InlineData("HW002-Table18.docx")]
+ public void HW002(string name)
+ {
+ var sourceDocxFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-2-Source.docx")));
+ var sourceCopiedToDestHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-2-Source.html")));
+ var destCssFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-3.css")));
+ var destDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-4-ConvertedByHtmlToWml.docx")));
+ var annotatedHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-5-Annotated.txt")));
+
+ File.Copy(sourceDocxFi.FullName, sourceCopiedToDestDocxFi.FullName);
+
+ WordAutomationUtilities.SaveAsHtmlUsingWord(sourceDocxFi, sourceCopiedToDestHtmlFi);
+ XElement html = null;
+ int cnt = 0;
+ while (true)
+ {
+ try
+ {
+ html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceCopiedToDestHtmlFi);
+ break;
+ }
+ catch (XmlException e)
+ {
+ throw e;
+ }
+ catch (IOException i)
+ {
+ if (++cnt == 20)
+ throw i;
+ System.Threading.Thread.Sleep(50);
+ continue;
+ }
+ }
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+ File.WriteAllText(destCssFi.FullName, usedAuthorCss);
+
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings();
+ // image references in HTML files contain the path to the subdir that contains the images, so base URI is the name of the directory
+ // that contains the HTML files
+ settings.BaseUriForImages = Path.Combine(TestUtil.TempDir.FullName);
+
+ WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(
+ defaultCss,
+ usedAuthorCss,
+ userCss,
+ html,
+ settings,
+ null, // use the default EmptyDocument
+ s_ProduceAnnotatedHtml ? annotatedHtmlFi.FullName : null);
+
+ Assert.NotNull(doc);
+
+ if (doc != null)
+ SaveValidateAndFormatMainDocPart(destDocxFi, doc);
+
+#if DO_CONVERSION_VIA_WORD
+ var newAltChunkBeforeFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-5-AltChunkBefore.docx")));
+ var newAltChunkAfterFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-6-ConvertedViaWord.docx")));
+ WordAutomationUtilities.DoConversionViaWord(newAltChunkBeforeFi, newAltChunkAfterFi, html);
+#endif
+ }
+
+ [Theory]
+ [InlineData("T0015.html")]
+ public void HW003(string name)
+ {
+ string testDocPrefix = "HW003_";
+ var sourceHtmlFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, (testDocPrefix + sourceHtmlFi.Name).Replace(".html", "-1-Source.html")));
+ var destCssFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, (testDocPrefix + sourceHtmlFi.Name).Replace(".html", "-2.css")));
+ var destDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, (testDocPrefix + sourceHtmlFi.Name).Replace(".html", "-3-ConvertedByHtmlToWml.docx")));
+ var annotatedHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, (testDocPrefix + sourceHtmlFi.Name).Replace(".html", "-4-Annotated.txt")));
+
+ File.Copy(sourceHtmlFi.FullName, sourceCopiedToDestHtmlFi.FullName);
+ XElement html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceCopiedToDestHtmlFi);
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+ File.WriteAllText(destCssFi.FullName, usedAuthorCss);
+
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings();
+ settings.BaseUriForImages = Path.Combine(TestUtil.TempDir.FullName);
+ settings.DefaultBlockContentMargin = "36pt";
+
+ WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(defaultCss, usedAuthorCss, userCss, html, settings, null, s_ProduceAnnotatedHtml ? annotatedHtmlFi.FullName : null);
+ Assert.NotNull(doc);
+ if (doc != null)
+ SaveValidateAndFormatMainDocPart(destDocxFi, doc);
+ }
+
+ [Theory]
+ [InlineData("HW010-Symbols01.docx")]
+ [InlineData("HW010-Symbols02.docx")]
+ [InlineData("HW010-TableWithEmptyRows.docx")]
+ [InlineData("HW010-TableWithThreeEmptyRows.docx")]
+ [InlineData("HW010-TableWithImage.docx")]
+ [InlineData("HW010-SpanWithSingleSpace.docx")]
+ [InlineData("HW010-Tab01.docx")]
+
+ public void HW010(string name)
+ {
+ var sourceDocxFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-2-Source.docx")));
+ var sourceCopiedToDestHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-2-Source.html")));
+ var destCssFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-3.css")));
+ var destDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-4-ConvertedByHtmlToWml.docx")));
+ var annotatedHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-5-Annotated.txt")));
+
+ File.Copy(sourceDocxFi.FullName, sourceCopiedToDestDocxFi.FullName);
+
+ SaveAsHtmlUsingHtmlConverter(sourceCopiedToDestDocxFi.FullName, sourceCopiedToDestDocxFi.DirectoryName);
+ XElement html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceCopiedToDestHtmlFi);
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+ File.WriteAllText(destCssFi.FullName, usedAuthorCss);
+
+ var settingsWmlDocument = new WmlDocument(sourceCopiedToDestDocxFi.FullName);
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings(settingsWmlDocument);
+ // image references in HTML files contain the path to the subdir that contains the images, so base URI is the name of the directory
+ // that contains the HTML files
+ settings.BaseUriForImages = Path.Combine(TestUtil.TempDir.FullName);
+
+ WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(
+ defaultCss,
+ usedAuthorCss,
+ userCss,
+ html,
+ settings,
+ null, // use the default EmptyDocument
+ s_ProduceAnnotatedHtml ? annotatedHtmlFi.FullName : null);
+
+ Assert.NotNull(doc);
+
+ if (doc != null)
+ SaveValidateAndFormatMainDocPart(destDocxFi, doc);
+
+#if DO_CONVERSION_VIA_WORD
+ var newAltChunkBeforeFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-5-AltChunkBefore.docx")));
+ var newAltChunkAfterFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-6-ConvertedViaWord.docx")));
+ WordAutomationUtilities.DoConversionViaWord(newAltChunkBeforeFi, newAltChunkAfterFi, html);
+#endif
+ }
+
+ private static void SaveAsHtmlUsingHtmlConverter(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ Console.WriteLine(fi.Name);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+
+ var pageTitle = fi.FullName;
+ var part = wDoc.CoreFilePropertiesPart;
+ if (part != null)
+ {
+ pageTitle = (string)part.GetXDocument().Descendants(DC.title).FirstOrDefault() ?? fi.FullName;
+ }
+
+ // TODO: Determine max-width from size of content area.
+ HtmlConverterSettings settings = new HtmlConverterSettings()
+ {
+ AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ imageFormat = ImageFormat.Png;
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ string imageSource = localDirInfo.Name + "/image" +
+ imageCounter.ToString() + "." + extension;
+
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageSource),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement htmlElement = HtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Produce HTML document with <!DOCTYPE html > declaration to tell the browser
+ // we are using HTML5.
+ var html = new XDocument(
+ new XDocumentType("html", null, null, null),
+ htmlElement);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+
+ private static void SaveValidateAndFormatMainDocPart(FileInfo destDocxFi, WmlDocument doc)
+ {
+ WmlDocument formattedDoc;
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(doc.DocumentByteArray, 0, doc.DocumentByteArray.Length);
+ using (WordprocessingDocument document = WordprocessingDocument.Open(ms, true))
+ {
+ XDocument xDoc = document.MainDocumentPart.GetXDocument();
+ document.MainDocumentPart.PutXDocumentWithFormatting();
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(document);
+ var errorsString = errors
+ .Select(e => e.Description + Environment.NewLine)
+ .StringConcatenate();
+
+ // Assert that there were no errors in the generated document.
+ Assert.Equal("", errorsString);
+ }
+ formattedDoc = new WmlDocument(destDocxFi.FullName, ms.ToArray());
+ }
+ formattedDoc.SaveAs(destDocxFi.FullName);
+ }
+
+
+ /*
+ * display property:
+ * - inline
+ * - block
+ * - list-item
+ * - inline-block
+ * - table
+ * - inline-table
+ * - table-row-group
+ * - table-header-group
+ * - table-footer-group
+ * - table-row
+ * - table-column-group
+ * - table-column
+ * - table-cell
+ * - table-caption
+ * - none
+ * - inherit
+ *
+ * position property:
+ * - static
+ * - relative
+ * - absolute
+ * - fixed
+ * - inherit
+ *
+ * top, left, bottom, right properties:
+ * (only apply if position property is not static)
+ */
+
+ static string defaultCss =
+ @"html, address,
+blockquote,
+body, dd, div,
+dl, dt, fieldset, form,
+frame, frameset,
+h1, h2, h3, h4,
+h5, h6, noframes,
+ol, p, ul, center,
+dir, hr, menu, pre { display: block; unicode-bidi: embed }
+li { display: list-item }
+head { display: none }
+table { display: table }
+tr { display: table-row }
+thead { display: table-header-group }
+tbody { display: table-row-group }
+tfoot { display: table-footer-group }
+col { display: table-column }
+colgroup { display: table-column-group }
+td, th { display: table-cell }
+caption { display: table-caption }
+th { font-weight: bolder; text-align: center }
+caption { text-align: center }
+body { margin: auto; }
+h1 { font-size: 2em; margin: auto; }
+h2 { font-size: 1.5em; margin: auto; }
+h3 { font-size: 1.17em; margin: auto; }
+h4, p,
+blockquote, ul,
+fieldset, form,
+ol, dl, dir,
+menu { margin: auto }
+a { color: blue; }
+h5 { font-size: .83em; margin: auto }
+h6 { font-size: .75em; margin: auto }
+h1, h2, h3, h4,
+h5, h6, b,
+strong { font-weight: bolder }
+blockquote { margin-left: 40px; margin-right: 40px }
+i, cite, em,
+var, address { font-style: italic }
+pre, tt, code,
+kbd, samp { font-family: monospace }
+pre { white-space: pre }
+button, textarea,
+input, select { display: inline-block }
+big { font-size: 1.17em }
+small, sub, sup { font-size: .83em }
+sub { vertical-align: sub }
+sup { vertical-align: super }
+table { border-spacing: 2px; }
+thead, tbody,
+tfoot { vertical-align: middle }
+td, th, tr { vertical-align: inherit }
+s, strike, del { text-decoration: line-through }
+hr { border: 1px inset }
+ol, ul, dir,
+menu, dd { margin-left: 40px }
+ol { list-style-type: decimal }
+ol ul, ul ol,
+ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
+u, ins { text-decoration: underline }
+br:before { content: ""\A""; white-space: pre-line }
+center { text-align: center }
+:link, :visited { text-decoration: underline }
+:focus { outline: thin dotted invert }
+/* Begin bidirectionality settings (do not change) */
+BDO[DIR=""ltr""] { direction: ltr; unicode-bidi: bidi-override }
+BDO[DIR=""rtl""] { direction: rtl; unicode-bidi: bidi-override }
+*[DIR=""ltr""] { direction: ltr; unicode-bidi: embed }
+*[DIR=""rtl""] { direction: rtl; unicode-bidi: embed }
+
+";
+
+ // original defaultCss
+ // static string defaultCss =
+ // @"html, address,
+ //blockquote,
+ //body, dd, div,
+ //dl, dt, fieldset, form,
+ //frame, frameset,
+ //h1, h2, h3, h4,
+ //h5, h6, noframes,
+ //ol, p, ul, center,
+ //dir, hr, menu, pre { display: block; unicode-bidi: embed }
+ //li { display: list-item }
+ //head { display: none }
+ //table { display: table }
+ //tr { display: table-row }
+ //thead { display: table-header-group }
+ //tbody { display: table-row-group }
+ //tfoot { display: table-footer-group }
+ //col { display: table-column }
+ //colgroup { display: table-column-group }
+ //td, th { display: table-cell }
+ //caption { display: table-caption }
+ //th { font-weight: bolder; text-align: center }
+ //caption { text-align: center }
+ //body { margin: 8px; }
+ //h1 { font-size: 2em; margin: .67em 0em; }
+ //h2 { font-size: 1.5em; margin: .75em 0em; }
+ //h3 { font-size: 1.17em; margin: .83em 0em; }
+ //h4, p,
+ //blockquote, ul,
+ //fieldset, form,
+ //ol, dl, dir,
+ //menu { margin: 1.12em 0 }
+ //a { color: blue; }
+ //h5 { font-size: .83em; margin: 1.5em 0 }
+ //h6 { font-size: .75em; margin: 1.67em 0 }
+ //h1, h2, h3, h4,
+ //h5, h6, b,
+ //strong { font-weight: bolder }
+ //blockquote { margin-left: 40px; margin-right: 40px }
+ //i, cite, em,
+ //var, address { font-style: italic }
+ //pre, tt, code,
+ //kbd, samp { font-family: monospace }
+ //pre { white-space: pre }
+ //button, textarea,
+ //input, select { display: inline-block }
+ //big { font-size: 1.17em }
+ //small, sub, sup { font-size: .83em }
+ //sub { vertical-align: sub }
+ //sup { vertical-align: super }
+ //table { border-spacing: 2px; }
+ //thead, tbody,
+ //tfoot { vertical-align: middle }
+ //td, th, tr { vertical-align: inherit }
+ //s, strike, del { text-decoration: line-through }
+ //hr { border: 1px inset }
+ //ol, ul, dir,
+ //menu, dd { margin-left: 40px }
+ //ol { list-style-type: decimal }
+ //ol ul, ul ol,
+ //ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
+ //u, ins { text-decoration: underline }
+ //br:before { content: ""\A""; white-space: pre-line }
+ //center { text-align: center }
+ //:link, :visited { text-decoration: underline }
+ //:focus { outline: thin dotted invert }
+ ///* Begin bidirectionality settings (do not change) */
+ //BDO[DIR=""ltr""] { direction: ltr; unicode-bidi: bidi-override }
+ //BDO[DIR=""rtl""] { direction: rtl; unicode-bidi: bidi-override }
+ //*[DIR=""ltr""] { direction: ltr; unicode-bidi: embed }
+ //*[DIR=""rtl""] { direction: rtl; unicode-bidi: embed }";
+
+ static string userCss = @"";
+ }
+}
diff --git a/OpenXmlPowerTools.Tests.OA/ListItemRetrieverTests.cs b/OpenXmlPowerTools.Tests.OA/ListItemRetrieverTests.cs
new file mode 100644
index 0000000..a87fffe
--- /dev/null
+++ b/OpenXmlPowerTools.Tests.OA/ListItemRetrieverTests.cs
@@ -0,0 +1,271 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using HtmlAgilityPack;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+
+namespace OxPt
+{
+ public class LiTests
+ {
+
+ // PowerShell oneliner that generates InlineData for all files in a directory
+ // dir | % { '[InlineData("' + $_.Name + '")]' } | clip
+
+ [Theory]
+ [InlineData("LIR001-en-US-ordinal.docx")]
+ [InlineData("LIR002-en-US-ordinalText.docx")]
+ [InlineData("LIR003-en-US-upperLetter.docx")]
+ [InlineData("LIR004-en-US-upperRoman.docx")]
+ [InlineData("LIR005-fr-FR-cardinalText.docx")]
+ [InlineData("LIR006-fr-FR-ordinalText.docx")]
+ [InlineData("LIR006-fr-FR-ordinal.docx")]
+ // [InlineData("LIR007-ru-RU-ordinalText.docx")] // todo this fails, the code needs updated.
+ [InlineData("LIR008-zh-CH-chineseCountingThousand.docx")]
+ [InlineData("LIR009-zh-CN-chineseCounting.docx")]
+ [InlineData("LIR010-zh-CN-ideographTraditional.docx")]
+ [InlineData("LIR011-en-US-00001.docx")]
+ [InlineData("LIR012-en-US-0001.docx")]
+ [InlineData("LIR013-en-US-001.docx")]
+ [InlineData("LIR014-en-US-01.docx")]
+ [InlineData("LIR015-en-US-cardinalText.docx")]
+ [InlineData("LIR016-en-US-decimal.docx")]
+ [InlineData("LIR017-en-US-decimalEnclosedCircle.docx")]
+ [InlineData("LIR018-en-US-decimalZero.docx")]
+ [InlineData("LIR019-en-US-lowerLetter.docx")]
+ [InlineData("LIR020-en-US-lowerRoman.docx")]
+ public void LIR001(string file)
+ {
+ FileInfo lirFile = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, file));
+ WmlDocument wmlDoc = new WmlDocument(lirFile.FullName);
+
+ var wordHtmlFile = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, lirFile.Name.Replace(".docx", "-Word.html")));
+ WordAutomationUtilities.SaveAsHtmlUsingWord(lirFile, wordHtmlFile);
+
+ var ptHtmlFile = ConvertToHtml(lirFile.FullName, TestUtil.TempDir.FullName);
+ var fiPtXml = SaveHtmlAsXml(ptHtmlFile);
+
+ // read and write to get the BOM on the file
+ var wh = File.ReadAllText(wordHtmlFile.FullName, Encoding.Default);
+ File.WriteAllText(wordHtmlFile.FullName, wh, Encoding.UTF8);
+
+ var wordXml = SaveHtmlAsXml(wordHtmlFile);
+ CompareNumbering(fiPtXml, wordXml);
+ }
+
+ private static void CompareNumbering(FileInfo fiPtXml, FileInfo wordXml)
+ {
+ char splitChar = '|';
+
+ Console.WriteLine("Comparing {0} to {1}", fiPtXml.Name, wordXml.Name);
+ var xdWord = XDocument.Load(wordXml.FullName);
+ List<string> wordRawParagraphText = xdWord.Descendants("h2").Select(p => p.DescendantNodes().OfType<XText>().Select(t => t.Value).Aggregate((s, i) => s + i)).ToList();
+ var wordTextToCompare = wordRawParagraphText.Where(p => p.Contains(splitChar.ToString())).Select(p => p.Split(splitChar)[0]).ToList();
+
+ var xdPt = XDocument.Load(fiPtXml.FullName);
+ XNamespace xhtml = "http://www.w3.org/1999/xhtml";
+ List<string> ptRawParagraphText = xdPt.Descendants(xhtml + "h2").Select(p => p.DescendantNodes().OfType<XText>().Select(t => t.Value).Aggregate((s, i) => s + i)).ToList();
+ var ptTextToCompare = ptRawParagraphText.Where(p => p.Contains(splitChar.ToString())).Select(p => new
+ {
+ ListItem = p.Split(splitChar)[0],
+ ParaText = p.Split(splitChar)[1],
+ }).ToList();
+
+ if (!wordTextToCompare.Any())
+ {
+ throw new Exception("Internal error - no items selected");
+ }
+ if (wordTextToCompare.Count() != ptTextToCompare.Count())
+ {
+ Assert.True(false);
+ //Console.WriteLine("Error, differing line counts");
+ //Console.WriteLine("Word line count: {0}", wordTextToCompare.Count());
+ //Console.WriteLine("Pt line count: {0}", ptTextToCompare.Count());
+ return;
+ }
+ var zipped = wordTextToCompare.Zip(ptTextToCompare, (w, p) => new
+ {
+ WordText = w,
+ PtText = p.ListItem,
+ ParagraphText = p.ParaText,
+ });
+ var mismatchList = zipped.Where(z =>
+ {
+ var w = z.WordText.Replace('\n', ' ').Trim();
+ var p = z.PtText.Replace('\n', ' ').Trim();
+ var match = w == p;
+ if (match)
+ return false;
+ return true;
+ }).Select(z => z.ParagraphText).ToList();
+ if (mismatchList.Any())
+ {
+ Assert.True(false);
+ //Console.WriteLine("Mismatches: {0}", mismatchList.Count());
+ //foreach (var item in mismatchList.Take(20))
+ //{
+ // Console.WriteLine(item);
+ //}
+ }
+ }
+
+ public static FileInfo ConvertToHtml(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ FileInfo destFileName;
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+ var pageTitle = (string)wDoc.CoreFilePropertiesPart.GetXDocument().Descendants(DC.title).FirstOrDefault();
+ if (pageTitle == null)
+ pageTitle = fi.FullName;
+
+ HtmlConverterSettings settings = new HtmlConverterSettings()
+ {
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ {
+ // Convert png to jpeg.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageFileName),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement html = HtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ return destFileName;
+ }
+
+ public static FileInfo SaveHtmlAsXml(FileInfo htmlFileName)
+ {
+ string baseName = htmlFileName.Name.Substring(0, htmlFileName.Name.Length - htmlFileName.Extension.Length);
+ FileInfo destFile = new FileInfo(Path.Combine(htmlFileName.DirectoryName, baseName + ".xml"));
+
+ HtmlDocument hdoc = new HtmlDocument();
+ hdoc.Load(htmlFileName.FullName, Encoding.UTF8);
+ hdoc.OptionOutputAsXml = true;
+ hdoc.Save(destFile.FullName, Encoding.UTF8);
+ StringBuilder sb = new StringBuilder(File.ReadAllText(destFile.FullName, Encoding.Default));
+ sb.Replace("è", "è");
+ sb.Replace("&", "&");
+ sb.Replace(" ", "\xA0");
+ sb.Replace(""", "\"");
+ sb.Replace("<", "~lt;");
+ sb.Replace(">", "~gt;");
+ sb.Replace("&#", "~#");
+ sb.Replace("&", "&");
+ sb.Replace("~lt;", "<");
+ sb.Replace("~gt;", ">");
+ sb.Replace("~#", "&#");
+ File.WriteAllText(destFile.FullName, sb.ToString(), Encoding.UTF8);
+ return destFile;
+ }
+ }
+
+}
diff --git a/OpenXmlPowerTools.Tests.OA/OpenXmlPowerTools.Tests.OA.csproj b/OpenXmlPowerTools.Tests.OA/OpenXmlPowerTools.Tests.OA.csproj
new file mode 100644
index 0000000..37303a9
--- /dev/null
+++ b/OpenXmlPowerTools.Tests.OA/OpenXmlPowerTools.Tests.OA.csproj
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="..\packages\xunit.runner.visualstudio.2.0.1\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.0.1\build\net20\xunit.runner.visualstudio.props')" />
+ <Import Project="..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props" Condition="Exists('..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props')" />
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{2C50A5AF-E34B-4FC4-983F-F30BB49A57AB}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>OpenXmlPowerTools.Tests.OA</RootNamespace>
+ <AssemblyName>OpenXmlPowerTools.Tests.OA</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <NuGetPackageImportStamp>f5d754ea</NuGetPackageImportStamp>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>TRACE;DEBUG;USE_HTMLAGILITYPACK</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="DocumentFormat.OpenXml, Version=2.5.5631.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\Open-Xml-SDK\DocumentFormat.OpenXml\bin\Debug\DocumentFormat.OpenXml.dll</HintPath>
+ </Reference>
+ <Reference Include="HtmlAgilityPack, Version=1.3.9.1, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\HtmlAgilityPack\HtmlAgilityPack.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.Office.Interop.Word, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
+ <EmbedInteropTypes>True</EmbedInteropTypes>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.IO.Packaging">
+ <HintPath>..\..\Open-Xml-SDK\DocumentFormat.OpenXml\bin\Debug\System.IO.Packaging.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ <Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
+ <HintPath>..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="xunit.assert, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
+ <HintPath>..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="xunit.core, Version=2.0.0.2929, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c, processorArchitecture=MSIL">
+ <HintPath>..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\OpenXmlPowerTools.Tests\HtmlConverterTests.cs">
+ <Link>HtmlConverterTests.cs</Link>
+ </Compile>
+ <Compile Include="..\OpenXmlPowerTools.Tests\HtmlToWmlConverterTests.cs">
+ <Link>HtmlToWmlConverterTests.cs</Link>
+ </Compile>
+ <Compile Include="..\OpenXmlPowerTools.Tests\HtmlToWmlReadAsXElement.cs">
+ <Link>HtmlToWmlReadAsXElement.cs</Link>
+ </Compile>
+ <Compile Include="HtmlToWmlConverterTests2.cs" />
+ <Compile Include="ListItemRetrieverTests.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="WordAutomationUtilities.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\OpenXmlPowerTools\OpenXmlPowerTools.csproj">
+ <Project>{6f957ff3-afcc-4d69-8fbc-71ae21bc45c9}</Project>
+ <Name>OpenXmlPowerTools</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+ <PropertyGroup>
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
+ </PropertyGroup>
+ <Error Condition="!Exists('..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.core.2.0.0\build\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.props'))" />
+ <Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.0.1\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.0.1\build\net20\xunit.runner.visualstudio.props'))" />
+ </Target>
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerTools.Tests.OA/WordAutomationUtilities.cs b/OpenXmlPowerTools.Tests.OA/WordAutomationUtilities.cs
new file mode 100644
index 0000000..ef154e1
--- /dev/null
+++ b/OpenXmlPowerTools.Tests.OA/WordAutomationUtilities.cs
@@ -0,0 +1,158 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Word = Microsoft.Office.Interop.Word;
+
+namespace OxPt
+{
+ public class WordAutomationUtilities
+ {
+ public static void DoConversionViaWord(FileInfo newAltChunkBeforeFi, FileInfo newAltChunkAfterFi, XElement html)
+ {
+ var blankAltChunkFi = new DirectoryInfo(Path.Combine(TestUtil.SourceDir.FullName, "Blank-altchunk.docx"));
+ File.Copy(blankAltChunkFi.FullName, newAltChunkBeforeFi.FullName);
+ using (WordprocessingDocument myDoc = WordprocessingDocument.Open(newAltChunkBeforeFi.FullName, true))
+ {
+ string altChunkId = "AltChunkId1";
+ MainDocumentPart mainPart = myDoc.MainDocumentPart;
+ AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(
+ "application/xhtml+xml", altChunkId);
+ using (Stream chunkStream = chunk.GetStream(FileMode.Create, FileAccess.Write))
+ using (StreamWriter stringStream = new StreamWriter(chunkStream))
+ stringStream.Write(html.ToString());
+ XElement altChunk = new XElement(W.altChunk,
+ new XAttribute(R.id, altChunkId)
+ );
+ XDocument mainDocumentXDoc = myDoc.MainDocumentPart.GetXDocument();
+ mainDocumentXDoc.Root
+ .Element(W.body)
+ .AddFirst(altChunk);
+ myDoc.MainDocumentPart.PutXDocument();
+ }
+
+ WordAutomationUtilities.OpenAndSaveAs(newAltChunkBeforeFi.FullName, newAltChunkAfterFi.FullName);
+
+ while (true)
+ {
+ try
+ {
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(newAltChunkAfterFi.FullName, true))
+ {
+ SimplifyMarkupSettings settings2 = new SimplifyMarkupSettings
+ {
+ RemoveMarkupForDocumentComparison = true,
+ };
+ MarkupSimplifier.SimplifyMarkup(wDoc, settings2);
+ XElement newRoot = (XElement)RemoveDivTransform(wDoc.MainDocumentPart.GetXDocument().Root);
+ wDoc.MainDocumentPart.GetXDocument().Root.ReplaceWith(newRoot);
+ wDoc.MainDocumentPart.PutXDocumentWithFormatting();
+ }
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ continue;
+ }
+ }
+ }
+
+ private static object RemoveDivTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.divId)
+ return null;
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RemoveDivTransform(n)));
+ }
+ return node;
+ }
+
+ public static void SaveAsHtmlUsingWord(FileInfo src, FileInfo dest)
+ {
+ Word.Application app = new Word.Application();
+ app.Visible = false;
+ try
+ {
+ Word.Document doc = app.Documents.Open(src.FullName);
+ doc.SaveAs2(dest.FullName, Word.WdSaveFormat.wdFormatFilteredHTML);
+ }
+ catch (System.Runtime.InteropServices.COMException)
+ {
+ Console.WriteLine("Caught unexpected COM exception.");
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ Environment.Exit(0);
+ }
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ }
+
+ public static void OpenAndSaveAs(string fromFileName, string toFileName)
+ {
+ Word.Application app = new Word.Application();
+ app.Visible = false;
+ FileInfo fi = new FileInfo(fromFileName);
+ try
+ {
+ FileInfo ffi = new FileInfo(fromFileName);
+ Word.Document doc = app.Documents.Open(ffi.FullName);
+ object FileFormat = Word.WdSaveFormat.wdFormatDocument;
+ FileInfo tfi = new FileInfo(toFileName);
+ object FileName = tfi.FullName;
+
+ doc.SaveAs(tfi.FullName, Word.WdSaveFormat.wdFormatDocumentDefault);
+ }
+ catch (System.Runtime.InteropServices.COMException)
+ {
+ Console.WriteLine("Caught unexpected COM exception.");
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ Environment.Exit(0);
+ }
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ }
+
+ public static void OpenAndSaveAs(FileInfo src, FileInfo dest)
+ {
+ Word.Application app = new Word.Application();
+ app.Visible = false;
+ try
+ {
+ Word.Document doc = app.Documents.Open(src.FullName);
+ doc.SaveAs2(dest.FullName, Word.WdSaveFormat.wdFormatDocument);
+ }
+ catch (System.Runtime.InteropServices.COMException)
+ {
+ Console.WriteLine("Caught unexpected COM exception.");
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ Environment.Exit(0);
+ }
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ }
+ }
+}
diff --git a/OpenXmlPowerTools.Tests/ChartUpdaterTests.cs b/OpenXmlPowerTools.Tests/ChartUpdaterTests.cs
new file mode 100644
index 0000000..5afa3c2
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/ChartUpdaterTests.cs
@@ -0,0 +1,253 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class CuTests
+ {
+ [Theory]
+ [InlineData("CU001-Chart-Cached-Data-01.docx")]
+ [InlineData("CU002-Chart-Cached-Data-02.docx")]
+ [InlineData("CU003-Chart-Cached-Data-03.docx")]
+ [InlineData("CU004-Chart-Cached-Data-04.docx")]
+ [InlineData("CU005-Chart-Cached-Data-05.docx")]
+ [InlineData("CU006-Chart-Cached-Data-06.docx")]
+ [InlineData("CU007-Chart-Cached-Data-07.docx")]
+ [InlineData("CU008-Chart-Cached-Data-08.docx")]
+
+ [InlineData("CU009-Chart-Embedded-Xlsx-01.docx")]
+ [InlineData("CU010-Chart-Embedded-Xlsx-02.docx")]
+ [InlineData("CU011-Chart-Embedded-Xlsx-03.docx")]
+ [InlineData("CU012-Chart-Embedded-Xlsx-04.docx")]
+ [InlineData("CU013-Chart-Embedded-Xlsx-05.docx")]
+ [InlineData("CU014-Chart-Embedded-Xlsx-06.docx")]
+ [InlineData("CU015-Chart-Embedded-Xlsx-07.docx")]
+ [InlineData("CU016-Chart-Embedded-Xlsx-08.docx")]
+ [InlineData("CU017-Chart-Embedded-Xlsx-10.docx")]
+
+ [InlineData("CU018-Chart-Cached-Data-41.pptx")]
+ [InlineData("CU019-Chart-Embedded-Xlsx-41.pptx")]
+
+ public void CU001(string name)
+ {
+ FileInfo templateFile = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ if (templateFile.Extension.ToLower() == ".docx")
+ {
+ WmlDocument wmlTemplate = new WmlDocument(templateFile.FullName);
+
+ var afterUpdatingDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, templateFile.Name.Replace(".docx", "-processed-by-ChartUpdater.docx")));
+ wmlTemplate.SaveAs(afterUpdatingDocx.FullName);
+
+ using (var wDoc = WordprocessingDocument.Open(afterUpdatingDocx.FullName, true))
+ {
+ var chart1Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Car",
+ "Truck",
+ "Van",
+ "Bike",
+ "Boat",
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Q1",
+ "Q2",
+ "Q3",
+ "Q4",
+ },
+ Values = new double[][] {
+ new double[] {
+ 100, 310, 220, 450,
+ },
+ new double[] {
+ 200, 300, 350, 411,
+ },
+ new double[] {
+ 80, 120, 140, 600,
+ },
+ new double[] {
+ 120, 100, 140, 400,
+ },
+ new double[] {
+ 200, 210, 210, 480,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart1", chart1Data);
+
+ var chart2Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Series"
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Cars",
+ "Trucks",
+ "Vans",
+ "Boats",
+ },
+ Values = new double[][] {
+ new double[] {
+ 320, 112, 64, 80,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart2", chart2Data);
+
+ var chart3Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "X1",
+ "X2",
+ "X3",
+ "X4",
+ "X5",
+ "X6",
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Y1",
+ "Y2",
+ "Y3",
+ "Y4",
+ "Y5",
+ "Y6",
+ },
+ Values = new double[][] {
+ new double[] { 3.0, 2.1, .7, .7, 2.1, 3.0, },
+ new double[] { 3.0, 2.1, .8, .8, 2.1, 3.0, },
+ new double[] { 3.0, 2.4, 1.2, 1.2, 2.4, 3.0, },
+ new double[] { 3.0, 2.7, 1.7, 1.7, 2.7, 3.0, },
+ new double[] { 3.0, 2.9, 2.5, 2.5, 2.9, 3.0, },
+ new double[] { 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart3", chart3Data);
+
+ var chart4Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Car",
+ "Truck",
+ "Van",
+ },
+ CategoryDataType = ChartDataType.DateTime,
+ CategoryFormatCode = 14,
+ CategoryNames = new[] {
+ ToExcelInteger(new DateTime(2013, 9, 1)),
+ ToExcelInteger(new DateTime(2013, 9, 2)),
+ ToExcelInteger(new DateTime(2013, 9, 3)),
+ ToExcelInteger(new DateTime(2013, 9, 4)),
+ ToExcelInteger(new DateTime(2013, 9, 5)),
+ ToExcelInteger(new DateTime(2013, 9, 6)),
+ ToExcelInteger(new DateTime(2013, 9, 7)),
+ ToExcelInteger(new DateTime(2013, 9, 8)),
+ ToExcelInteger(new DateTime(2013, 9, 9)),
+ ToExcelInteger(new DateTime(2013, 9, 10)),
+ ToExcelInteger(new DateTime(2013, 9, 11)),
+ ToExcelInteger(new DateTime(2013, 9, 12)),
+ ToExcelInteger(new DateTime(2013, 9, 13)),
+ ToExcelInteger(new DateTime(2013, 9, 14)),
+ ToExcelInteger(new DateTime(2013, 9, 15)),
+ ToExcelInteger(new DateTime(2013, 9, 16)),
+ ToExcelInteger(new DateTime(2013, 9, 17)),
+ ToExcelInteger(new DateTime(2013, 9, 18)),
+ ToExcelInteger(new DateTime(2013, 9, 19)),
+ ToExcelInteger(new DateTime(2013, 9, 20)),
+ },
+ Values = new double[][] {
+ new double[] {
+ 1, 2, 3, 2, 3, 4, 5, 4, 5, 6, 5, 4, 5, 6, 7, 8, 7, 8, 8, 9,
+ },
+ new double[] {
+ 2, 3, 3, 4, 4, 5, 6, 7, 8, 7, 8, 9, 9, 9, 7, 8, 9, 9, 10, 11,
+ },
+ new double[] {
+ 2, 3, 3, 3, 3, 2, 2, 2, 3, 2, 3, 3, 4, 4, 4, 3, 4, 5, 5, 4,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart4", chart4Data);
+ }
+ }
+ if (templateFile.Extension.ToLower() == ".pptx")
+ {
+ PmlDocument pmlTemplate = new PmlDocument(templateFile.FullName);
+
+ var afterUpdatingPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, templateFile.Name.Replace(".pptx", "-processed-by-ChartUpdater.pptx")));
+ pmlTemplate.SaveAs(afterUpdatingPptx.FullName);
+
+ using (var pDoc = PresentationDocument.Open(afterUpdatingPptx.FullName, true))
+ {
+ var chart1Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Car",
+ "Truck",
+ "Van",
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Q1",
+ "Q2",
+ "Q3",
+ "Q4",
+ },
+ Values = new double[][] {
+ new double[] {
+ 320, 310, 320, 330,
+ },
+ new double[] {
+ 201, 224, 230, 221,
+ },
+ new double[] {
+ 180, 200, 220, 230,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(pDoc, 1, chart1Data);
+ }
+ }
+ }
+
+ private static string ToExcelInteger(DateTime dateTime)
+ {
+ return dateTime.ToOADate().ToString();
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/DocumentAssemblerTests.cs b/OpenXmlPowerTools.Tests/DocumentAssemblerTests.cs
new file mode 100644
index 0000000..28bfe8e
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/DocumentAssemblerTests.cs
@@ -0,0 +1,256 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class DaTests
+ {
+ [Theory]
+ [InlineData("DA001-TemplateDocument.docx", "DA-Data.xml", false)]
+ [InlineData("DA002-TemplateDocument.docx", "DA-DataNotHighValueCust.xml", false)]
+ [InlineData("DA003-Select-XPathFindsNoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA004-Select-XPathFindsNoDataOptional.docx", "DA-Data.xml", false)]
+ [InlineData("DA005-SelectRowData-NoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA006-SelectTestValue-NoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA007-SelectRepeatingData-NoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA008-TableElementWithNoTable.docx", "DA-Data.xml", true)]
+ [InlineData("DA009-InvalidXPath.docx", "DA-Data.xml", true)]
+ [InlineData("DA010-InvalidXml.docx", "DA-Data.xml", true)]
+ [InlineData("DA011-SchemaError.docx", "DA-Data.xml", true)]
+ [InlineData("DA012-OtherMarkupTypes.docx", "DA-Data.xml", true)]
+ [InlineData("DA013-Runs.docx", "DA-Data.xml", false)]
+ [InlineData("DA014-TwoRuns-NoValuesSelected.docx", "DA-Data.xml", true)]
+ [InlineData("DA015-TwoRunsXmlExceptionInFirst.docx", "DA-Data.xml", true)]
+ [InlineData("DA016-TwoRunsSchemaErrorInSecond.docx", "DA-Data.xml", true)]
+ [InlineData("DA017-FiveRuns.docx", "DA-Data.xml", true)]
+ [InlineData("DA018-SmartQuotes.docx", "DA-Data.xml", false)]
+ [InlineData("DA019-RunIsEntireParagraph.docx", "DA-Data.xml", false)]
+ [InlineData("DA020-TwoRunsAndNoOtherContent.docx", "DA-Data.xml", true)]
+ [InlineData("DA021-NestedRepeat.docx", "DA-DataNestedRepeat.xml", false)]
+ [InlineData("DA022-InvalidXPath.docx", "DA-Data.xml", true)]
+ [InlineData("DA023-RepeatWOEndRepeat.docx", "DA-Data.xml", true)]
+ [InlineData("DA026-InvalidRootXmlElement.docx", "DA-Data.xml", true)]
+ [InlineData("DA027-XPathErrorInPara.docx", "DA-Data.xml", true)]
+ [InlineData("DA028-NoPrototypeRow.docx", "DA-Data.xml", true)]
+ [InlineData("DA029-NoDataForCell.docx", "DA-Data.xml", true)]
+ [InlineData("DA030-TooMuchDataForCell.docx", "DA-TooMuchDataForCell.xml", true)]
+ [InlineData("DA031-CellDataInAttributes.docx", "DA-CellDataInAttributes.xml", true)]
+ [InlineData("DA032-TooMuchDataForConditional.docx", "DA-TooMuchDataForConditional.xml", true)]
+ [InlineData("DA033-ConditionalOnAttribute.docx", "DA-ConditionalOnAttribute.xml", false)]
+ [InlineData("DA034-HeaderFooter.docx", "DA-Data.xml", false)]
+ [InlineData("DA035-SchemaErrorInRepeat.docx", "DA-Data.xml", true)]
+ [InlineData("DA036-SchemaErrorInConditional.docx", "DA-Data.xml", true)]
+
+ [InlineData("DA100-TemplateDocument.docx", "DA-Data.xml", false)]
+ [InlineData("DA101-TemplateDocument.docx", "DA-Data.xml", true)]
+ [InlineData("DA102-TemplateDocument.docx", "DA-Data.xml", true)]
+
+ [InlineData("DA201-TemplateDocument.docx", "DA-Data.xml", false)]
+ [InlineData("DA202-TemplateDocument.docx", "DA-DataNotHighValueCust.xml", false)]
+ [InlineData("DA203-Select-XPathFindsNoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA204-Select-XPathFindsNoDataOptional.docx", "DA-Data.xml", false)]
+ [InlineData("DA205-SelectRowData-NoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA206-SelectTestValue-NoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA207-SelectRepeatingData-NoData.docx", "DA-Data.xml", true)]
+ [InlineData("DA209-InvalidXPath.docx", "DA-Data.xml", true)]
+ [InlineData("DA210-InvalidXml.docx", "DA-Data.xml", true)]
+ [InlineData("DA211-SchemaError.docx", "DA-Data.xml", true)]
+ [InlineData("DA212-OtherMarkupTypes.docx", "DA-Data.xml", true)]
+ [InlineData("DA213-Runs.docx", "DA-Data.xml", false)]
+ [InlineData("DA214-TwoRuns-NoValuesSelected.docx", "DA-Data.xml", true)]
+ [InlineData("DA215-TwoRunsXmlExceptionInFirst.docx", "DA-Data.xml", true)]
+ [InlineData("DA216-TwoRunsSchemaErrorInSecond.docx", "DA-Data.xml", true)]
+ [InlineData("DA217-FiveRuns.docx", "DA-Data.xml", true)]
+ [InlineData("DA218-SmartQuotes.docx", "DA-Data.xml", false)]
+ [InlineData("DA219-RunIsEntireParagraph.docx", "DA-Data.xml", false)]
+ [InlineData("DA220-TwoRunsAndNoOtherContent.docx", "DA-Data.xml", true)]
+ [InlineData("DA221-NestedRepeat.docx", "DA-DataNestedRepeat.xml", false)]
+ [InlineData("DA222-InvalidXPath.docx", "DA-Data.xml", true)]
+ [InlineData("DA223-RepeatWOEndRepeat.docx", "DA-Data.xml", true)]
+ [InlineData("DA226-InvalidRootXmlElement.docx", "DA-Data.xml", true)]
+ [InlineData("DA227-XPathErrorInPara.docx", "DA-Data.xml", true)]
+ [InlineData("DA228-NoPrototypeRow.docx", "DA-Data.xml", true)]
+ [InlineData("DA229-NoDataForCell.docx", "DA-Data.xml", true)]
+ [InlineData("DA230-TooMuchDataForCell.docx", "DA-TooMuchDataForCell.xml", true)]
+ [InlineData("DA231-CellDataInAttributes.docx", "DA-CellDataInAttributes.xml", true)]
+ [InlineData("DA232-TooMuchDataForConditional.docx", "DA-TooMuchDataForConditional.xml", true)]
+ [InlineData("DA233-ConditionalOnAttribute.docx", "DA-ConditionalOnAttribute.xml", false)]
+ [InlineData("DA234-HeaderFooter.docx", "DA-Data.xml", false)]
+ [InlineData("DA235-Crashes.docx", "DA-Content-List.xml", false)]
+ [InlineData("DA236-Page-Num-in-Footer.docx", "DA-Content-List.xml", false)]
+ [InlineData("DA237-SchemaErrorInRepeat.docx", "DA-Data.xml", true)]
+ [InlineData("DA238-SchemaErrorInConditional.docx", "DA-Data.xml", true)]
+ [InlineData("DA239-RunLevelCC-Repeat.docx", "DA-Data.xml", false)]
+
+ [InlineData("DA250-ConditionalWithRichXPath.docx", "DA250-Address.xml", false)]
+ [InlineData("DA251-EnhancedTables.docx", "DA-Data.xml", false)]
+ [InlineData("DA252-Table-With-Sum.docx", "DA-Data.xml", false)]
+ [InlineData("DA253-Table-With-Sum-Run-Level-CC.docx", "DA-Data.xml", false)]
+ [InlineData("DA254-Table-With-XPath-Sum.docx", "DA-Data.xml", false)]
+ [InlineData("DA255-Table-With-XPath-Sum-Run-Level-CC.docx", "DA-Data.xml", false)]
+ [InlineData("DA256-NoInvalidDocOnErrorInRun.docx", "DA-Data.xml", true)]
+ [InlineData("DA257-OptionalRepeat.docx", "DA-Data.xml", false)]
+ [InlineData("DA258-ContentAcceptsCharsAsXPathResult.docx", "DA-Data.xml", false)]
+ [InlineData("DA259-MultiLineContents.docx", "DA-Data.xml", false)]
+ [InlineData("DA260-RunLevelRepeat.docx", "DA-Data.xml", false)]
+ [InlineData("DA261-RunLevelConditional.docx", "DA-Data.xml", false)]
+ [InlineData("DA262-ConditionalNotMatch.docx", "DA-Data.xml", false)]
+ [InlineData("DA263-ConditionalNotMatch.docx", "DA-DataSmallCustomer.xml", false)]
+ [InlineData("DA264-InvalidRunLevelRepeat.docx", "DA-Data.xml", true)]
+ [InlineData("DA265-RunLevelRepeatWithWhiteSpaceBefore.docx", "DA-Data.xml", false)]
+ [InlineData("DA266-RunLevelRepeat-NoData.docx", "DA-Data.xml", true)]
+
+ public void DA101(string name, string data, bool err)
+ {
+ FileInfo templateDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ FileInfo dataFile = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, data));
+
+ WmlDocument wmlTemplate = new WmlDocument(templateDocx.FullName);
+ XElement xmldata = XElement.Load(dataFile.FullName);
+
+ bool returnedTemplateError;
+ WmlDocument afterAssembling = DocumentAssembler.AssembleDocument(wmlTemplate, xmldata, out returnedTemplateError);
+ var assembledDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, templateDocx.Name.Replace(".docx", "-processed-by-DocumentAssembler.docx")));
+ afterAssembling.SaveAs(assembledDocx.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(afterAssembling.DocumentByteArray, 0, afterAssembling.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator v = new OpenXmlValidator();
+ var valErrors = v.Validate(wDoc).Where(ve => !s_ExpectedErrors.Contains(ve.Description));
+
+#if false
+ StringBuilder sb = new StringBuilder();
+ foreach (var item in valErrors.Select(r => r.Description).OrderBy(t => t).Distinct())
+ {
+ sb.Append(item).Append(Environment.NewLine);
+ }
+ string z = sb.ToString();
+ Console.WriteLine(z);
+#endif
+
+ Assert.Empty(valErrors);
+ }
+ }
+
+ Assert.Equal(err, returnedTemplateError);
+ }
+
+ [Theory]
+ [InlineData("DA259-MultiLineContents.docx", "DA-Data.xml", false)]
+ public void DA259(string name, string data, bool err)
+ {
+ DA101(name, data, err);
+ var assembledDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".docx", "-processed-by-DocumentAssembler.docx")));
+ WmlDocument afterAssembling = new WmlDocument(assembledDocx.FullName);
+ int brCount = afterAssembling.MainDocumentPart
+ .Element(W.body)
+ .Elements(W.p).ElementAt(1)
+ .Elements(W.r)
+ .Elements(W.br).Count();
+ Assert.Equal(4, brCount);
+ }
+
+ [Theory]
+ [InlineData("DA024-TrackedRevisions.docx", "DA-Data.xml")]
+ public void DA102_Throws(string name, string data)
+ {
+ FileInfo templateDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ FileInfo dataFile = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, data));
+
+ WmlDocument wmlTemplate = new WmlDocument(templateDocx.FullName);
+ XElement xmldata = XElement.Load(dataFile.FullName);
+
+ bool returnedTemplateError;
+ WmlDocument afterAssembling;
+ Assert.Throws<OpenXmlPowerToolsException>(() =>
+ {
+ afterAssembling = DocumentAssembler.AssembleDocument(wmlTemplate, xmldata, out returnedTemplateError);
+ });
+ }
+
+ [Theory]
+ [InlineData("DA025-TemplateDocument.docx", "DA-Data.xml", false)]
+ public void DA103_UseXmlDocument(string name, string data, bool err)
+ {
+ FileInfo templateDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ FileInfo dataFile = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, data));
+
+ WmlDocument wmlTemplate = new WmlDocument(templateDocx.FullName);
+ XmlDocument xmldata = new XmlDocument();
+ xmldata.Load(dataFile.FullName);
+
+ bool returnedTemplateError;
+ WmlDocument afterAssembling = DocumentAssembler.AssembleDocument(wmlTemplate, xmldata, out returnedTemplateError);
+ var assembledDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, templateDocx.Name.Replace(".docx", "-processed-by-DocumentAssembler.docx")));
+ afterAssembling.SaveAs(assembledDocx.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(afterAssembling.DocumentByteArray, 0, afterAssembling.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator v = new OpenXmlValidator();
+ var valErrors = v.Validate(wDoc).Where(ve => !s_ExpectedErrors.Contains(ve.Description));
+ Assert.Empty(valErrors);
+ }
+ }
+
+ Assert.Equal(err, returnedTemplateError);
+ }
+
+ private static List<string> s_ExpectedErrors = new List<string>()
+ {
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:evenHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:evenVBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRowFirstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRowLastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRowFirstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRowLastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noVBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:oddHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:oddVBand' attribute is not declared.",
+ };
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/DocumentBuilderTests.cs b/OpenXmlPowerTools.Tests/DocumentBuilderTests.cs
new file mode 100644
index 0000000..7d91e09
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/DocumentBuilderTests.cs
@@ -0,0 +1,805 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using DocumentFormat.OpenXml.Wordprocessing;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class DbTests
+ {
+ [Fact]
+ public void DB001_DocumentBuilderKeepSections()
+ {
+ string name = "DB001-Sections.docx";
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(sourceDocx.FullName), true),
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-processed-by-DocumentBuilder.docx")));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ }
+
+ [Fact]
+ public void DB002_DocumentBuilderKeepSectionsDiscardHeaders()
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB002-Sections-With-Headers.docx"));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB002-Landscape-Section.docx"));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ new Source(new WmlDocument(source2Docx.FullName)) { KeepSections = true, DiscardHeadersAndFootersInKeptSections = true },
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB002-Keep-Sections-Discard-Headers-And-Footers.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ }
+
+ [Fact]
+ public void DB003_DocumentBuilderOnlyDefaultHeader()
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB003-Only-Default-Header.docx"));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB002-Landscape-Section.docx"));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ new Source(new WmlDocument(source2Docx.FullName)) { KeepSections = true, DiscardHeadersAndFootersInKeptSections = true },
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB003-Only-Default-Header.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ }
+
+ [Fact]
+ public void DB004_DocumentBuilderNoHeaders()
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB004-No-Headers.docx"));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB002-Landscape-Section.docx"));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ new Source(new WmlDocument(source2Docx.FullName)) { KeepSections = true, DiscardHeadersAndFootersInKeptSections = true },
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB003-Only-Default-Header.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ }
+
+ [Fact]
+ public void DB005_HeadersWithRefsToImages()
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB005-Headers-With-Images.docx"));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB002-Landscape-Section.docx"));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ new Source(new WmlDocument(source2Docx.FullName)) { KeepSections = true, DiscardHeadersAndFootersInKeptSections = true },
+ new Source(new WmlDocument(source1Docx.FullName)) { KeepSections = true },
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB005.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ }
+
+ [Fact]
+ public void DB006_Example_DocumentBuilder01()
+ {
+ FileInfo source1 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB006-Source1.docx"));
+ FileInfo source2 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB006-Source2.docx"));
+ FileInfo source3 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB006-Source3.docx"));
+ List<Source> sources = null;
+
+ // Create new document from 10 paragraphs starting at paragraph 5 of Source1.docx
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName), 5, 10, true),
+ };
+ var out1 = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB006-Out1.docx"));
+ DocumentBuilder.BuildDocument(sources, out1.FullName);
+ Validate(out1);
+
+ // Create new document from paragraph 1, and paragraphs 5 through end of Source3.docx.
+ // This effectively 'deletes' paragraphs 2-4
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source3.FullName), 0, 1, false),
+ new Source(new WmlDocument(source3.FullName), 4, false),
+ };
+ var out2 = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB006-Out2.docx"));
+ DocumentBuilder.BuildDocument(sources, out2.FullName);
+ Validate(out2);
+
+ // Create a new document that consists of the entirety of Source1.docx and Source2.docx. Use
+ // the section information (headings and footers) from source1.
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName), true),
+ new Source(new WmlDocument(source2.FullName), false),
+ };
+ var out3 = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB006-Out3.docx"));
+ DocumentBuilder.BuildDocument(sources, out3.FullName);
+ Validate(out3);
+
+ // Create a new document that consists of the entirety of Source1.docx and Source2.docx. Use
+ // the section information (headings and footers) from source2.
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName), false),
+ new Source(new WmlDocument(source2.FullName), true),
+ };
+ var out4 = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB006-Out4.docx"));
+ DocumentBuilder.BuildDocument(sources, out4.FullName);
+ Validate(out4);
+
+ // Create a new document that consists of the first 5 paragraphs of Source1.docx and the first
+ // five paragraphs of Source2.docx. This example returns a new WmlDocument, when you then can
+ // serialize to a SharePoint document library, or use in some other interesting scenario.
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName), 0, 5, false),
+ new Source(new WmlDocument(source2.FullName), 0, 5, true),
+ };
+ WmlDocument wmlOut5 = DocumentBuilder.BuildDocument(sources);
+ var out5 = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB006-Out5.docx"));
+
+ wmlOut5.SaveAs(out5.FullName); // save it to the file system, but we could just as easily done something
+ // else with it.
+ Validate(out5);
+ }
+
+ [Fact]
+ public void DB007_Example_DocumentBuilder02_WhitePaper()
+ {
+ FileInfo spec = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB007-Spec.docx"));
+ FileInfo whitePaper = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB007-WhitePaper.docx"));
+ FileInfo paperAbstract = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB007-Abstract.docx"));
+ FileInfo authorBio = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB007-AuthorBiography.docx"));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(whitePaper.FullName), 0, 1, true),
+ new Source(new WmlDocument(paperAbstract.FullName), false),
+ new Source(new WmlDocument(authorBio.FullName), false),
+ new Source(new WmlDocument(whitePaper.FullName), 1, false),
+ };
+ var assembledPaper = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB007-AssembledPaper.docx"));
+ DocumentBuilder.BuildDocument(sources, assembledPaper.FullName);
+ Validate(assembledPaper);
+ }
+
+ [Fact]
+ public void DB008_DeleteParasWithGivenStyle()
+ {
+ FileInfo notes = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB007-Notes.docx"));
+
+ List<Source> sources = null;
+ // Delete all paragraphs with a specific style.
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(notes.FullName, false))
+ {
+ sources = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Select((p, i) => new
+ {
+ Paragraph = p,
+ Index = i,
+ })
+ .GroupAdjacent(pi => (string)pi.Paragraph
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault() != "Note")
+ .Where(g => g.Key == true)
+ .Select(g => new Source(
+ new WmlDocument(notes.FullName), g.First().Index,
+ g.Last().Index - g.First().Index + 1, true))
+ .ToList();
+ }
+ var newNotes = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB008-NewNotes.docx"));
+ DocumentBuilder.BuildDocument(sources, newNotes.FullName);
+ Validate(newNotes);
+ }
+
+ [Theory]
+ [InlineData("DB009-00010", "DB/HeadersFooters/Src/Content-Controls.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00020", "DB/HeadersFooters/Src/Letterhead.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00030", "DB/HeadersFooters/Src/Letterhead-with-Watermark.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00040", "DB/HeadersFooters/Src/Logo.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00050", "DB/HeadersFooters/Src/Watermark-1.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00060", "DB/HeadersFooters/Src/Watermark-2.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00070", "DB/HeadersFooters/Src/Disclaimer.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00080", "DB/HeadersFooters/Src/Footer.docx", "DB/HeadersFooters/Dest/Fax.docx", "Templafy")]
+ [InlineData("DB009-00110", "DB/HeadersFooters/Src/Content-Controls.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00120", "DB/HeadersFooters/Src/Letterhead.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00130", "DB/HeadersFooters/Src/Letterhead-with-Watermark.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00140", "DB/HeadersFooters/Src/Logo.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00150", "DB/HeadersFooters/Src/Watermark-1.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00160", "DB/HeadersFooters/Src/Watermark-2.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00170", "DB/HeadersFooters/Src/Disclaimer.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+ [InlineData("DB009-00180", "DB/HeadersFooters/Src/Footer.docx", "DB/HeadersFooters/Dest/Letter.docx", "Templafy")]
+
+ public void DB009_ImportIntoHeadersFooters(string testId, string src, string dest, string insertId)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Load the source document
+ FileInfo sourceDocxFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, src));
+ WmlDocument wmlSourceDocument = new WmlDocument(sourceDocxFi.FullName);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Load the dest document
+ FileInfo destDocxFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, dest));
+ WmlDocument wmlDestDocument = new WmlDocument(destDocxFi.FullName);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Create the dir for the test
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id: " + testId);
+ else
+ thisTestTempDir.Create();
+ var tempDirFullName = thisTestTempDir.FullName;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Copy src DOCX to temp directory, for ease of review
+
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var sourceDocxCopiedToDestFileName = new FileInfo(Path.Combine(tempDirFullName, sourceDocxFi.Name));
+ if (!sourceDocxCopiedToDestFileName.Exists)
+ wmlSourceDocument.SaveAs(sourceDocxCopiedToDestFileName.FullName);
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Copy dest DOCX to temp directory, for ease of review
+
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var destDocxCopiedToDestFileName = new FileInfo(Path.Combine(tempDirFullName, destDocxFi.Name));
+ if (!destDocxCopiedToDestFileName.Exists)
+ wmlDestDocument.SaveAs(destDocxCopiedToDestFileName.FullName);
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+
+ List<Source> sources = new List<Source>()
+ {
+ new Source(wmlDestDocument),
+ new Source(wmlSourceDocument, insertId),
+ };
+
+ var outFi = new FileInfo(Path.Combine(tempDirFullName, "Output.docx"));
+ DocumentBuilder.BuildDocument(sources, outFi.FullName);
+ Validate(outFi);
+ }
+
+#if false
+ [Theory]
+ [InlineData("DB999-00010", "DBTEMP/03DE57384B87AA6C2A3BDE87DDDD7F880DC55E.docx", true)]
+ [InlineData("DB999-00020", "DBTEMP/0D3DEB27ED036116466BED616B2056CDD2783A.docx", false)]
+ [InlineData("DB999-00030", "DBTEMP/421628B3F4B03B123CA8EDDA5009E449F5F47D.docx", false)]
+ [InlineData("DB999-00040", "DBTEMP/58D4E8661C7F44FE33392B89B0A3CB0AF1684F.docx", false)]
+ [InlineData("DB999-00050", "DBTEMP/67EBCA627D6D584CAB3EB1DF2E4C3982023DEE.docx", true)]
+ [InlineData("DB999-00060", "DBTEMP/A529643E2FC3E2C682FA86DEE0A1B3064DCEE0.docx", false)]
+ [InlineData("DB999-00070", "DBTEMP/E794032F0422B440D3C564F0E09E395519127D.docx", false)]
+ [InlineData("DB999-00080", "DBTEMP/1FF1ADF30B24978E9449754459C743D3BC67ED.docx", false)]
+ [InlineData("DB999-00090", "DBTEMP/5E685927DA2FECB88DE9CAF0BECEC88BC118A7.docx", false)]
+ [InlineData("DB999-00100", "DBTEMP/6427BCF5C18B55D627B95F3E14924050628C5B.docx", false)]
+ [InlineData("DB999-00110", "DBTEMP/91691E0D3AB89E9927A2BAC5D385BB6277648F.docx", false)]
+ [InlineData("DB999-00120", "DBTEMP/9533BC5710190EA01DA86D29CD06880395C4AF.docx", false)]
+ [InlineData("DB999-00130", "DBTEMP/E9CD8C556AA52CA7D31DADB51A201EEF580AA8.docx", false)]
+ [InlineData("DB999-00140", "DBTEMP/21D3CE149C30B791F9A8BE092828E1469A9047.docx", false)]
+ [InlineData("DB999-00150", "DBTEMP/AC0CB8CE43A7ECAE995BB542D4FB1060FB835B.docx", false)]
+ [InlineData("DB999-00160", "DBTEMP/C61F69B52EC8B0E2C784C932B26F3C613AE671.docx", false)]
+ [InlineData("DB999-00170", "DBTEMP/1DF04A9130B3EF858ACA6837A706A429904973.dotm", false)]
+ [InlineData("DB999-00180", "DBTEMP/6E9F26B708DE6076B2C731B97AAA5288D839AB.docm", false)]
+ [InlineData("DB999-00190", "DBTEMP/A6649726EA0BD7545932DDD51403D83E4D5917.docx", false)]
+ [InlineData("DB999-00200", "DBTEMP/C8AE8AD0A73F24B7CFCFD11918B337CF2B90C9.docx", false)]
+ [InlineData("DB999-00210", "DBTEMP/BC46A7FBB212EFD10878A39D91AE3ECAADDAB0.docx", false)]
+ [InlineData("DB999-00220", "DBTEMP/B6F0E938B508676B322C47F3E0E29C8D786DB2.docm", false)]
+ [InlineData("DB999-00230", "DBTEMP/D4D8694A51DECA243AF748B3232BE565EEE19D.docx", false)]
+ [InlineData("DB999-00240", "DBTEMP/F20B3CE72BF635462E22BA3CA81CA9D57F6FEB.docx", false)]
+ [InlineData("DB999-00250", "DBTEMP/74ED106FF88C1B195D97C466E00BECCB636A03.docx", false)]
+ [InlineData("DB999-00260", "DBTEMP/4421A4B7B6ECC2813070309AA2D86C4BCA4AEF.docx", false)]
+ [InlineData("DB999-00270", "DBTEMP/BC7D91B993807518F3D430B7C6592AFD6BD91C.docx", false)]
+ [InlineData("DB999-00280", "DBTEMP/3006E76FE65E8A25A91ED204EEBEE6D6D62A44.docx", false)]
+ [InlineData("DB999-00290", "DBTEMP/6254B74778BFFCD1799F4F2B3B01C2025AABB2.docx", false)]
+ [InlineData("DB999-00300", "DBTEMP/5AD0A0BD99676B268D8E7C1F69238FB9B6149E.docx", false)]
+ [InlineData("DB999-00310", "DBTEMP/2D58495ECCF30ED9507B707C689CA9C9D4B049.docx", false)]
+
+ public void DB999_DocumentBuilder(string testId, string src, bool shouldThrow)
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Load the source document
+ FileInfo sourceDocxFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, src));
+ WmlDocument wmlSourceDocument = new WmlDocument(sourceDocxFi.FullName);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Create the dir for the test
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id: " + testId);
+ else
+ thisTestTempDir.Create();
+ var tempDirFullName = thisTestTempDir.FullName;
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Copy src DOCX to temp directory, for ease of review
+
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var sourceDocxCopiedToDestFileName = new FileInfo(Path.Combine(tempDirFullName, sourceDocxFi.Name));
+ if (!sourceDocxCopiedToDestFileName.Exists)
+ wmlSourceDocument.SaveAs(sourceDocxCopiedToDestFileName.FullName);
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+
+ List<string> expectedErrors;
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlSourceDocument.DocumentByteArray, 0, wmlSourceDocument.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, false))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ expectedErrors = validator.Validate(wDoc)
+ .Select(e => e.Description)
+ .Distinct()
+ .ToList();
+ }
+ }
+ foreach (var item in s_ExpectedErrors)
+ expectedErrors.Add(item);
+
+ List<Source> sources = new List<Source>()
+ {
+ new Source(wmlSourceDocument, true),
+ };
+
+ var outFi = new FileInfo(Path.Combine(tempDirFullName, "Output.docx"));
+
+ if (shouldThrow)
+ {
+ Assert.Throws<DocumentBuilderException>(() => DocumentBuilder.BuildDocument(sources, outFi.FullName));
+ }
+ else
+ {
+ var outWml = DocumentBuilder.BuildDocument(sources);
+ outWml.SaveAs(outFi.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(outWml.DocumentByteArray, 0, outWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, false))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e =>
+ {
+ var str = e.Description;
+ foreach (var ee in expectedErrors)
+ {
+ if (str.Contains(ee))
+ return false;
+ }
+ return true;
+ });
+ if (errors.Count() != 0)
+ {
+ var message = errors.Select(e => e.Description + Environment.NewLine).StringConcatenate();
+ Assert.True(false, message);
+ }
+ }
+ }
+
+ }
+ }
+#endif
+
+ private class DocumentInfo
+ {
+ public int DocumentNumber;
+ public int Start;
+ public int Count;
+ }
+
+ [Fact]
+ public void DB009_ShredDocument()
+ {
+ FileInfo spec = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB007-Spec.docx"));
+ // Shred a document into multiple parts for each section
+ List<DocumentInfo> documentList;
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(spec.FullName, false))
+ {
+ var sectionCounts = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Rollup(0, (pi, last) => (string)pi
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault() == "Heading1" ? last + 1 : last);
+ var beforeZipped = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Select((p, i) => new
+ {
+ Paragraph = p,
+ Index = i,
+ });
+ var zipped = PtExtensions.PtZip(beforeZipped, sectionCounts, (pi, sc) => new
+ {
+ Paragraph = pi.Paragraph,
+ Index = pi.Index,
+ SectionIndex = sc,
+ });
+ documentList = zipped
+ .GroupAdjacent(p => p.SectionIndex)
+ .Select(g => new DocumentInfo
+ {
+ DocumentNumber = g.Key,
+ Start = g.First().Index,
+ Count = g.Last().Index - g.First().Index + 1,
+ })
+ .ToList();
+ }
+ foreach (var doc in documentList)
+ {
+ string fileName = String.Format("DB009-Section{0:000}.docx", doc.DocumentNumber);
+ var fiSection = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, fileName));
+ List<Source> documentSource = new List<Source> {
+ new Source(new WmlDocument(spec.FullName), doc.Start, doc.Count, true)
+ };
+ DocumentBuilder.BuildDocument(documentSource, fiSection.FullName);
+ Validate(fiSection);
+ }
+
+ // Re-assemble the parts into a single document.
+ List<Source> sources = TestUtil.TempDir
+ .GetFiles("DB009-Section*.docx")
+ .Select(d => new Source(new WmlDocument(d.FullName), true))
+ .ToList();
+ var fiReassembled = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB009-Reassembled.docx"));
+
+ DocumentBuilder.BuildDocument(sources, fiReassembled.FullName);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(fiReassembled.FullName, true))
+ {
+ ReferenceAdder.AddToc(doc, "/w:document/w:body/w:p[1]",
+ @"TOC \o '1-3' \h \z \u", null, null);
+ }
+ Validate(fiReassembled);
+ }
+
+
+ [Fact]
+ public void DB010_InsertUsingInsertId()
+ {
+ FileInfo front = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB010-FrontMatter.docx"));
+ FileInfo insert01 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB010-Insert-01.docx"));
+ FileInfo insert02 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB010-Insert-02.docx"));
+ FileInfo template = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB010-Template.docx"));
+
+ WmlDocument doc1 = new WmlDocument(template.FullName);
+ using (MemoryStream mem = new MemoryStream())
+ {
+ mem.Write(doc1.DocumentByteArray, 0, doc1.DocumentByteArray.Length);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(mem, true))
+ {
+ XDocument xDoc = doc.MainDocumentPart.GetXDocument();
+ XElement frontMatterPara = xDoc.Root.Descendants(W.txbxContent).Elements(W.p).FirstOrDefault();
+ frontMatterPara.ReplaceWith(
+ new XElement(PtOpenXml.Insert,
+ new XAttribute("Id", "Front")));
+ XElement tbl = xDoc.Root.Element(W.body).Elements(W.tbl).FirstOrDefault();
+ XElement firstCell = tbl.Descendants(W.tr).First().Descendants(W.p).First();
+ firstCell.ReplaceWith(
+ new XElement(PtOpenXml.Insert,
+ new XAttribute("Id", "Liz")));
+ XElement secondCell = tbl.Descendants(W.tr).Skip(1).First().Descendants(W.p).First();
+ secondCell.ReplaceWith(
+ new XElement(PtOpenXml.Insert,
+ new XAttribute("Id", "Eric")));
+ doc.MainDocumentPart.PutXDocument();
+ }
+ doc1.DocumentByteArray = mem.ToArray();
+ }
+
+ List<Source> sources = new List<Source>()
+ {
+ new Source(doc1, true),
+ new Source(new WmlDocument(insert01.FullName), "Liz"),
+ new Source(new WmlDocument(insert02.FullName), "Eric"),
+ new Source(new WmlDocument(front.FullName), "Front"),
+ };
+ var out1 = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB010-Inserted.docx"));
+ DocumentBuilder.BuildDocument(sources, out1.FullName);
+ Validate(out1);
+ }
+
+ [Fact]
+ public void DB011_BodyAndHeaderWithShapes()
+ {
+ // Both of theses documents have a shape with a DocProperties ID of 1.
+ FileInfo source1 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB011-Header-With-Shape.docx"));
+ FileInfo source2 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB011-Body-With-Shape.docx"));
+ List<Source> sources = null;
+
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName)),
+ new Source(new WmlDocument(source2.FullName)),
+ };
+ var processedDestDocx =
+ new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB011-Body-And-Header-With-Shapes.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ Validate(processedDestDocx);
+
+ ValidateUniqueDocPrIds(processedDestDocx);
+ }
+
+
+ [Fact]
+ public void DB012_NumberingsWithSameAbstractNumbering()
+ {
+ // This document has three numbering definitions that use the same abstract numbering definition.
+ string name = "DB012-Lists-With-Different-Numberings.docx";
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ List<Source> sources = null;
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(sourceDocx.FullName)),
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName,
+ sourceDocx.Name.Replace(".docx", "-processed-by-DocumentBuilder.docx")));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(processedDestDocx.FullName, false))
+ {
+ var numberingRoot = wDoc.MainDocumentPart.NumberingDefinitionsPart.GetXDocument().Root;
+ Assert.Equal(3, numberingRoot.Elements(W.num).Count());
+ }
+ }
+
+ [Fact]
+ public void DB013a_LocalizedStyleIds_Heading()
+ {
+ // Each of these documents have changed the font color of the Heading 1 style, one to red, the other to green.
+ // One of the documents were created with English as the Word display language, the other with Danish as the language.
+ FileInfo source1 =
+ new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB013a-Red-Heading1-English.docx"));
+ FileInfo source2 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName,
+ "DB013a-Green-Heading1-Danish.docx"));
+ List<Source> sources = null;
+
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName)),
+ new Source(new WmlDocument(source2.FullName)),
+ };
+ var processedDestDocx =
+ new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB013a-Colored-Heading1.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(processedDestDocx.FullName, false))
+ {
+ var styles = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument().Root.Elements(W.style).ToArray();
+ Assert.Equal(1, styles.Count(s => s.Element(W.name).Attribute(W.val).Value == "heading 1"));
+
+ var styleIds = new HashSet<string>(styles.Select(s => s.Attribute(W.styleId).Value));
+ var paragraphStylesIds = new HashSet<string>(wDoc.MainDocumentPart.GetXDocument()
+ .Descendants(W.pStyle)
+ .Select(p => p.Attribute(W.val).Value));
+ Assert.Subset(styleIds, paragraphStylesIds);
+ }
+ }
+
+ [Fact]
+ public void DB013b_LocalizedStyleIds_List()
+ {
+ // Each of these documents have changed the font color of the List Paragraph style, one to orange, the other to blue.
+ // One of the documents were created with English as the Word display language, the other with Danish as the language.
+ FileInfo source1 =
+ new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB013b-Orange-List-Danish.docx"));
+ FileInfo source2 = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName,
+ "DB013b-Blue-List-English.docx"));
+ List<Source> sources = null;
+
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1.FullName)),
+ new Source(new WmlDocument(source2.FullName)),
+ };
+ var processedDestDocx =
+ new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB013b-Colored-List.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(processedDestDocx.FullName, false))
+ {
+ var styles = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument().Root.Elements(W.style).ToArray();
+ Assert.Equal(1, styles.Count(s => s.Element(W.name).Attribute(W.val).Value == "List Paragraph"));
+
+ var styleIds = new HashSet<string>(styles.Select(s => s.Attribute(W.styleId).Value));
+ var paragraphStylesIds = new HashSet<string>(wDoc.MainDocumentPart.GetXDocument()
+ .Descendants(W.pStyle)
+ .Select(p => p.Attribute(W.val).Value));
+ Assert.Subset(styleIds, paragraphStylesIds);
+ }
+ }
+
+ [Fact]
+ public void DB014_KeepWebExtensions()
+ {
+ FileInfo source = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "DB014-WebExtensions.docx"));
+ List<Source> sources = null;
+
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source.FullName)),
+ };
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "DB014-WebExtensions.docx"));
+ DocumentBuilder.BuildDocument(sources, processedDestDocx.FullName);
+ Validate(processedDestDocx);
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(processedDestDocx.FullName, false))
+ {
+ Assert.NotNull(wDoc.WebExTaskpanesPart);
+ Assert.Equal(2, wDoc.WebExTaskpanesPart.Taskpanes.ChildElements.Count);
+ Assert.Equal(2, wDoc.WebExTaskpanesPart.WebExtensionParts.Count());
+ }
+ }
+
+ private void Validate(FileInfo fi)
+ {
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(fi.FullName, true))
+ {
+ OpenXmlValidator v = new OpenXmlValidator();
+ var errors = v.Validate(wDoc).Where(ve =>
+ {
+ var found = s_ExpectedErrors.Any(xe => ve.Description.Contains(xe));
+ return !found;
+ });
+
+ if (errors.Count() != 0)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (var item in errors)
+ {
+ sb.Append(item.Description).Append(Environment.NewLine);
+ }
+ var s = sb.ToString();
+ Assert.True(false, s);
+ }
+ }
+ }
+
+ private static List<string> s_ExpectedErrors = new List<string>()
+ {
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:evenHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:evenVBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRowFirstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRowLastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRowFirstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRowLastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noVBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:oddHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:oddVBand' attribute is not declared.",
+ "The element has unexpected child element 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:updateFields'.",
+ "The attribute 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:name' has invalid value 'useWord2013TrackBottomHyphenation'. The Enumeration constraint failed.",
+ "The 'http://schemas.microsoft.com/office/word/2012/wordml:restartNumberingAfterBreak' attribute is not declared.",
+ "Attribute 'id' should have unique value. Its current value '",
+ "The 'urn:schemas-microsoft-com:mac:vml:blur' attribute is not declared.",
+ "Attribute 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:id' should have unique value. Its current value '",
+ "The element has unexpected child element 'http://schemas.microsoft.com/office/word/2012/wordml:",
+ "The element has invalid child element 'http://schemas.microsoft.com/office/word/2012/wordml:",
+ "The 'urn:schemas-microsoft-com:mac:vml:complextextbox' attribute is not declared.",
+ "http://schemas.microsoft.com/office/word/2010/wordml:",
+ "http://schemas.microsoft.com/office/word/2008/9/12/wordml:",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:allStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:customStyles' attribute is not declared.",
+ };
+
+ private void ValidateUniqueDocPrIds(FileInfo fi)
+ {
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(fi.FullName, false))
+ {
+ var docPrIds = new HashSet<string>();
+ foreach (var item in doc.MainDocumentPart.GetXDocument().Descendants(WP.docPr))
+ Assert.True(docPrIds.Add(item.Attribute(NoNamespace.id).Value));
+ foreach (var header in doc.MainDocumentPart.HeaderParts)
+ foreach (var item in header.GetXDocument().Descendants(WP.docPr))
+ Assert.True(docPrIds.Add(item.Attribute(NoNamespace.id).Value));
+ foreach (var footer in doc.MainDocumentPart.FooterParts)
+ foreach (var item in footer.GetXDocument().Descendants(WP.docPr))
+ Assert.True(docPrIds.Add(item.Attribute(NoNamespace.id).Value));
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ foreach (var item in doc.MainDocumentPart.FootnotesPart.GetXDocument().Descendants(WP.docPr))
+ Assert.True(docPrIds.Add(item.Attribute(NoNamespace.id).Value));
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ foreach (var item in doc.MainDocumentPart.EndnotesPart.GetXDocument().Descendants(WP.docPr))
+ Assert.True(docPrIds.Add(item.Attribute(NoNamespace.id).Value));
+ }
+ }
+ }
+}
+#endif
diff --git a/OpenXmlPowerTools.Tests/HtmlConverterTests.cs b/OpenXmlPowerTools.Tests/HtmlConverterTests.cs
new file mode 100644
index 0000000..6c6bc37
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/HtmlConverterTests.cs
@@ -0,0 +1,400 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+#define COPY_FILES_FOR_DEBUGGING
+
+// DO_CONVERSION_VIA_WORD is defined in the project OpenXmlPowerTools.Tests.OA.csproj, but not in the OpenXmlPowerTools.Tests.csproj
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if DO_CONVERSION_VIA_WORD
+using Word = Microsoft.Office.Interop.Word;
+#endif
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class HcTests
+ {
+ public static bool s_CopySourceFiles = true;
+ public static bool s_CopyFormattingAssembledDocx = true;
+ public static bool s_ConvertUsingWord = true;
+
+ // PowerShell oneliner that generates InlineData for all files in a directory
+ // dir | % { '[InlineData("' + $_.Name + '")]' } | clip
+
+ [Theory]
+ [InlineData("HC001-5DayTourPlanTemplate.docx")]
+ [InlineData("HC002-Hebrew-01.docx")]
+ [InlineData("HC003-Hebrew-02.docx")]
+ [InlineData("HC004-ResumeTemplate.docx")]
+ [InlineData("HC005-TaskPlanTemplate.docx")]
+ [InlineData("HC006-Test-01.docx")]
+ [InlineData("HC007-Test-02.docx")]
+ [InlineData("HC008-Test-03.docx")]
+ [InlineData("HC009-Test-04.docx")]
+ [InlineData("HC010-Test-05.docx")]
+ [InlineData("HC011-Test-06.docx")]
+ [InlineData("HC012-Test-07.docx")]
+ [InlineData("HC013-Test-08.docx")]
+ [InlineData("HC014-RTL-Table-01.docx")]
+ [InlineData("HC015-Vertical-Spacing-atLeast.docx")]
+ [InlineData("HC016-Horizontal-Spacing-firstLine.docx")]
+ [InlineData("HC017-Vertical-Alignment-Cell-01.docx")]
+ [InlineData("HC018-Vertical-Alignment-Para-01.docx")]
+ [InlineData("HC019-Hidden-Run.docx")]
+ [InlineData("HC020-Small-Caps.docx")]
+ [InlineData("HC021-Symbols.docx")]
+ [InlineData("HC022-Table-Of-Contents.docx")]
+ [InlineData("HC023-Hyperlink.docx")]
+ [InlineData("HC024-Tabs-01.docx")]
+ [InlineData("HC025-Tabs-02.docx")]
+ [InlineData("HC026-Tabs-03.docx")]
+ [InlineData("HC027-Tabs-04.docx")]
+ [InlineData("HC028-No-Break-Hyphen.docx")]
+ [InlineData("HC029-Table-Merged-Cells.docx")]
+ [InlineData("HC030-Content-Controls.docx")]
+ [InlineData("HC031-Complicated-Document.docx")]
+ [InlineData("HC032-Named-Color.docx")]
+ [InlineData("HC033-Run-With-Border.docx")]
+ [InlineData("HC034-Run-With-Position.docx")]
+ [InlineData("HC035-Strike-Through.docx")]
+ [InlineData("HC036-Super-Script.docx")]
+ [InlineData("HC037-Sub-Script.docx")]
+ [InlineData("HC038-Conflicting-Border-Weight.docx")]
+ [InlineData("HC039-Bold.docx")]
+ [InlineData("HC040-Hyperlink-Fieldcode-01.docx")]
+ [InlineData("HC041-Hyperlink-Fieldcode-02.docx")]
+ [InlineData("HC042-Image-Png.docx")]
+ [InlineData("HC043-Chart.docx")]
+ [InlineData("HC044-Embedded-Workbook.docx")]
+ [InlineData("HC045-Italic.docx")]
+ [InlineData("HC046-BoldAndItalic.docx")]
+ [InlineData("HC047-No-Section.docx")]
+ [InlineData("HC048-Excerpt.docx")]
+ [InlineData("HC049-Borders.docx")]
+ [InlineData("HC050-Shaded-Text-01.docx")]
+ [InlineData("HC051-Shaded-Text-02.docx")]
+ [InlineData("HC060-Image-with-Hyperlink.docx")]
+ [InlineData("HC061-Hyperlink-in-Field.docx")]
+
+ public void HC001(string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+#if COPY_FILES_FOR_DEBUGGING
+ var sourceCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-1-Source.docx")));
+ if (!sourceCopiedToDestDocx.Exists)
+ File.Copy(sourceDocx.FullName, sourceCopiedToDestDocx.FullName);
+
+ var assembledFormattingDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-2-FormattingAssembled.docx")));
+ if (!assembledFormattingDestDocx.Exists)
+ CopyFormattingAssembledDocx(sourceDocx, assembledFormattingDestDocx);
+#endif
+
+ var oxPtConvertedDestHtml = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-3-OxPt.html")));
+ ConvertToHtml(sourceDocx, oxPtConvertedDestHtml);
+
+#if DO_CONVERSION_VIA_WORD
+ var wordConvertedDocHtml = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-4-Word.html")));
+ ConvertToHtmlUsingWord(sourceDocx, wordConvertedDocHtml);
+#endif
+
+ }
+
+ [Theory]
+ [InlineData("HC006-Test-01.docx")]
+ public void HC002_NoCssClasses(string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var oxPtConvertedDestHtml = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-5-OxPt-No-CSS-Classes.html")));
+ ConvertToHtmlNoCssClasses(sourceDocx, oxPtConvertedDestHtml);
+ }
+
+ private static void CopyFormattingAssembledDocx(FileInfo source, FileInfo dest)
+ {
+ var ba = File.ReadAllBytes(source.FullName);
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(ba, 0, ba.Length);
+ using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(ms, true))
+ {
+
+ RevisionAccepter.AcceptRevisions(wordDoc);
+ SimplifyMarkupSettings simplifyMarkupSettings = new SimplifyMarkupSettings
+ {
+ RemoveComments = true,
+ RemoveContentControls = true,
+ RemoveEndAndFootNotes = true,
+ RemoveFieldCodes = false,
+ RemoveLastRenderedPageBreak = true,
+ RemovePermissions = true,
+ RemoveProof = true,
+ RemoveRsidInfo = true,
+ RemoveSmartTags = true,
+ RemoveSoftHyphens = true,
+ RemoveGoBackBookmark = true,
+ ReplaceTabsWithSpaces = false,
+ };
+ MarkupSimplifier.SimplifyMarkup(wordDoc, simplifyMarkupSettings);
+
+ FormattingAssemblerSettings formattingAssemblerSettings = new FormattingAssemblerSettings
+ {
+ RemoveStyleNamesFromParagraphAndRunProperties = false,
+ ClearStyles = false,
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ CreateHtmlConverterAnnotationAttributes = true,
+ OrderElementsPerStandard = false,
+ ListItemRetrieverSettings =
+ new ListItemRetrieverSettings()
+ {
+ ListItemTextImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations,
+ },
+ };
+
+ FormattingAssembler.AssembleFormatting(wordDoc, formattingAssemblerSettings);
+ }
+ var newBa = ms.ToArray();
+ File.WriteAllBytes(dest.FullName, newBa);
+ }
+ }
+
+ private static void ConvertToHtml(FileInfo sourceDocx, FileInfo destFileName)
+ {
+ byte[] byteArray = File.ReadAllBytes(sourceDocx.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var outputDirectory = destFileName.Directory;
+ destFileName = new FileInfo(Path.Combine(outputDirectory.FullName, destFileName.Name));
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+ var pageTitle = (string)wDoc.CoreFilePropertiesPart.GetXDocument().Descendants(DC.title).FirstOrDefault();
+ if (pageTitle == null)
+ pageTitle = sourceDocx.FullName;
+
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
+ {
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ {
+ // Convert png to jpeg.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageFileName),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement html = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+
+ private static void ConvertToHtmlNoCssClasses(FileInfo sourceDocx, FileInfo destFileName)
+ {
+ byte[] byteArray = File.ReadAllBytes(sourceDocx.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var outputDirectory = destFileName.Directory;
+ destFileName = new FileInfo(Path.Combine(outputDirectory.FullName, destFileName.Name));
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+ var pageTitle = (string)wDoc.CoreFilePropertiesPart.GetXDocument().Descendants(DC.title).FirstOrDefault();
+ if (pageTitle == null)
+ pageTitle = sourceDocx.FullName;
+
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
+ {
+ PageTitle = pageTitle,
+ FabricateCssClasses = false,
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ {
+ // Convert png to jpeg.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageFileName),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement html = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+
+#if DO_CONVERSION_VIA_WORD
+ public static void ConvertToHtmlUsingWord(FileInfo sourceFileName, FileInfo destFileName)
+ {
+ Word.Application app = new Word.Application();
+ app.Visible = false;
+ try
+ {
+ Word.Document doc = app.Documents.Open(sourceFileName.FullName);
+ doc.SaveAs2(destFileName.FullName, Word.WdSaveFormat.wdFormatFilteredHTML);
+ }
+ catch (System.Runtime.InteropServices.COMException)
+ {
+ Console.WriteLine("Caught unexpected COM exception.");
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ Environment.Exit(0);
+ }
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ }
+#endif
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/HtmlToWmlConverterTests.cs b/OpenXmlPowerTools.Tests/HtmlToWmlConverterTests.cs
new file mode 100644
index 0000000..7b86389
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/HtmlToWmlConverterTests.cs
@@ -0,0 +1,534 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+using System.Text.RegularExpressions;
+
+/*******************************************************************************************
+ * HtmlToWmlConverter expects the HTML to be passed as an XElement, i.e. as XML. While the HTML test files that
+ * are included in Open-Xml-PowerTools are able to be read as XML, most HTML is not able to be read as XML.
+ * The best solution is to use the HtmlAgilityPack, which can parse HTML and save as XML. The HtmlAgilityPack
+ * is licensed under the Ms-PL (same as Open-Xml-PowerTools) so it is convenient to include it in your solution,
+ * and thereby you can convert HTML to XML that can be processed by the HtmlToWmlConverter.
+ *
+ * A convenient way to get the DLL that has been checked out with HtmlToWmlConverter is to clone the repo at
+ * https://github.com/EricWhiteDev/HtmlAgilityPack
+ *
+ * That repo contains only the DLL that has been checked out with HtmlToWmlConverter.
+ *
+ * Of course, you can also get the HtmlAgilityPack source and compile it to get the DLL. You can find it at
+ * http://codeplex.com/HtmlAgilityPack
+ *
+ * We don't include the HtmlAgilityPack in Open-Xml-PowerTools, to simplify installation. The XUnit tests in
+ * this module do not require the HtmlAgilityPack to run.
+*******************************************************************************************/
+
+#if DO_CONVERSION_VIA_WORD
+using Word = Microsoft.Office.Interop.Word;
+#endif
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class HwTests
+ {
+ static bool s_ProduceAnnotatedHtml = true;
+
+ // PowerShell oneliner that generates InlineData for all files in a directory
+ // dir | % { '[InlineData("' + $_.Name + '")]' } | clip
+
+ [Theory]
+ [InlineData("T0010.html")]
+ [InlineData("T0011.html")]
+ [InlineData("T0012.html")]
+ [InlineData("T0013.html")]
+ [InlineData("T0014.html")]
+ [InlineData("T0015.html")]
+ [InlineData("T0020.html")]
+ [InlineData("T0030.html")]
+ [InlineData("T0040.html")]
+ [InlineData("T0050.html")]
+ [InlineData("T0060.html")]
+ [InlineData("T0070.html")]
+ [InlineData("T0080.html")]
+ [InlineData("T0090.html")]
+ [InlineData("T0100.html")]
+ [InlineData("T0110.html")]
+ [InlineData("T0111.html")]
+ [InlineData("T0112.html")]
+ [InlineData("T0120.html")]
+ [InlineData("T0130.html")]
+ [InlineData("T0140.html")]
+ [InlineData("T0150.html")]
+ [InlineData("T0160.html")]
+ [InlineData("T0170.html")]
+ [InlineData("T0180.html")]
+ [InlineData("T0190.html")]
+ [InlineData("T0200.html")]
+ [InlineData("T0210.html")]
+ [InlineData("T0220.html")]
+ [InlineData("T0230.html")]
+ [InlineData("T0240.html")]
+ [InlineData("T0250.html")]
+ [InlineData("T0251.html")]
+ [InlineData("T0260.html")]
+ [InlineData("T0270.html")]
+ [InlineData("T0280.html")]
+ [InlineData("T0290.html")]
+ [InlineData("T0300.html")]
+ [InlineData("T0310.html")]
+ [InlineData("T0320.html")]
+ [InlineData("T0330.html")]
+ [InlineData("T0340.html")]
+ [InlineData("T0350.html")]
+ [InlineData("T0360.html")]
+ [InlineData("T0370.html")]
+ [InlineData("T0380.html")]
+ [InlineData("T0390.html")]
+ [InlineData("T0400.html")]
+ [InlineData("T0410.html")]
+ [InlineData("T0420.html")]
+ [InlineData("T0430.html")]
+ [InlineData("T0431.html")]
+ [InlineData("T0432.html")]
+ [InlineData("T0440.html")]
+ [InlineData("T0450.html")]
+ [InlineData("T0460.html")]
+ [InlineData("T0470.html")]
+ [InlineData("T0480.html")]
+ [InlineData("T0490.html")]
+ [InlineData("T0500.html")]
+ [InlineData("T0510.html")]
+ [InlineData("T0520.html")]
+ [InlineData("T0530.html")]
+ [InlineData("T0540.html")]
+ [InlineData("T0550.html")]
+ [InlineData("T0560.html")]
+ [InlineData("T0570.html")]
+ [InlineData("T0580.html")]
+ [InlineData("T0590.html")]
+ [InlineData("T0600.html")]
+ [InlineData("T0610.html")]
+ [InlineData("T0620.html")]
+ [InlineData("T0622.html")]
+ [InlineData("T0630.html")]
+ [InlineData("T0640.html")]
+ [InlineData("T0650.html")]
+ [InlineData("T0651.html")]
+ [InlineData("T0660.html")]
+ [InlineData("T0670.html")]
+ [InlineData("T0680.html")]
+ [InlineData("T0690.html")]
+ [InlineData("T0691.html")]
+ [InlineData("T0692.html")]
+ [InlineData("T0700.html")]
+ [InlineData("T0710.html")]
+ [InlineData("T0720.html")]
+ [InlineData("T0730.html")]
+ [InlineData("T0740.html")]
+ [InlineData("T0742.html")]
+ [InlineData("T0745.html")]
+ [InlineData("T0750.html")]
+ [InlineData("T0760.html")]
+ [InlineData("T0770.html")]
+ [InlineData("T0780.html")]
+ [InlineData("T0790.html")]
+ [InlineData("T0791.html")]
+ [InlineData("T0792.html")]
+ [InlineData("T0793.html")]
+ [InlineData("T0794.html")]
+ [InlineData("T0795.html")]
+ [InlineData("T0802.html")]
+ [InlineData("T0804.html")]
+ [InlineData("T0805.html")]
+ [InlineData("T0810.html")]
+ [InlineData("T0812.html")]
+ [InlineData("T0814.html")]
+ [InlineData("T0820.html")]
+ [InlineData("T0821.html")]
+ [InlineData("T0830.html")]
+ [InlineData("T0840.html")]
+ [InlineData("T0850.html")]
+ [InlineData("T0851.html")]
+ [InlineData("T0860.html")]
+ [InlineData("T0870.html")]
+ [InlineData("T0880.html")]
+ [InlineData("T0890.html")]
+ [InlineData("T0900.html")]
+ [InlineData("T0910.html")]
+ [InlineData("T0920.html")]
+ [InlineData("T0921.html")]
+ [InlineData("T0922.html")]
+ [InlineData("T0923.html")]
+ [InlineData("T0924.html")]
+ [InlineData("T0925.html")]
+ [InlineData("T0926.html")]
+ [InlineData("T0927.html")]
+ [InlineData("T0928.html")]
+ [InlineData("T0929.html")]
+ [InlineData("T0930.html")]
+ [InlineData("T0931.html")]
+ [InlineData("T0932.html")]
+ [InlineData("T0933.html")]
+ [InlineData("T0934.html")]
+ [InlineData("T0935.html")]
+ [InlineData("T0936.html")]
+ [InlineData("T0940.html")]
+ [InlineData("T0945.html")]
+ [InlineData("T0948.html")]
+ [InlineData("T0950.html")]
+ [InlineData("T0955.html")]
+ [InlineData("T0960.html")]
+ [InlineData("T0968.html")]
+ [InlineData("T0970.html")]
+ [InlineData("T0980.html")]
+ [InlineData("T0990.html")]
+ [InlineData("T1000.html")]
+ [InlineData("T1010.html")]
+ [InlineData("T1020.html")]
+ [InlineData("T1030.html")]
+ [InlineData("T1040.html")]
+ [InlineData("T1050.html")]
+ [InlineData("T1060.html")]
+ [InlineData("T1070.html")]
+ [InlineData("T1080.html")]
+ [InlineData("T1100.html")]
+ [InlineData("T1110.html")]
+ [InlineData("T1111.html")]
+ [InlineData("T1112.html")]
+ [InlineData("T1120.html")]
+ [InlineData("T1130.html")]
+ [InlineData("T1131.html")]
+ [InlineData("T1132.html")]
+ [InlineData("T1140.html")]
+ [InlineData("T1150.html")]
+ [InlineData("T1160.html")]
+ [InlineData("T1170.html")]
+ [InlineData("T1180.html")]
+ [InlineData("T1190.html")]
+ [InlineData("T1200.html")]
+ [InlineData("T1201.html")]
+ [InlineData("T1210.html")]
+ [InlineData("T1220.html")]
+ [InlineData("T1230.html")]
+ [InlineData("T1240.html")]
+ [InlineData("T1241.html")]
+ [InlineData("T1242.html")]
+ [InlineData("T1250.html")]
+ [InlineData("T1251.html")]
+ [InlineData("T1260.html")]
+ [InlineData("T1270.html")]
+ [InlineData("T1280.html")]
+ [InlineData("T1290.html")]
+ [InlineData("T1300.html")]
+ [InlineData("T1310.html")]
+ [InlineData("T1320.html")]
+ [InlineData("T1330.html")]
+ [InlineData("T1340.html")]
+ [InlineData("T1350.html")]
+ [InlineData("T1360.html")]
+ [InlineData("T1370.html")]
+ [InlineData("T1380.html")]
+ [InlineData("T1390.html")]
+ [InlineData("T1400.html")]
+ [InlineData("T1410.html")]
+ [InlineData("T1420.html")]
+ [InlineData("T1430.html")]
+ [InlineData("T1440.html")]
+ [InlineData("T1450.html")]
+ [InlineData("T1460.html")]
+ [InlineData("T1470.html")]
+ [InlineData("T1480.html")]
+ [InlineData("T1490.html")]
+ [InlineData("T1500.html")]
+ [InlineData("T1510.html")]
+ [InlineData("T1520.html")]
+ [InlineData("T1530.html")]
+ [InlineData("T1540.html")]
+ [InlineData("T1550.html")]
+ [InlineData("T1560.html")]
+ [InlineData("T1570.html")]
+ [InlineData("T1580.html")]
+ [InlineData("T1590.html")]
+ [InlineData("T1591.html")]
+ [InlineData("T1610.html")]
+ [InlineData("T1620.html")]
+ [InlineData("T1630.html")]
+ [InlineData("T1640.html")]
+ [InlineData("T1650.html")]
+ [InlineData("T1660.html")]
+ [InlineData("T1670.html")]
+ [InlineData("T1680.html")]
+ [InlineData("T1690.html")]
+ [InlineData("T1700.html")]
+ [InlineData("T1710.html")]
+ [InlineData("T1800.html")]
+ [InlineData("T1810.html")]
+ [InlineData("T1820.html")]
+ [InlineData("T1830.html")]
+ [InlineData("T1840.html")]
+ [InlineData("T1850.html")]
+
+ public void HW001(string name)
+ {
+#if false
+ string[] cssFilter = new[] {
+ "text-indent",
+ "margin-left",
+ "margin-right",
+ "padding-left",
+ "padding-right",
+ };
+#else
+ string[] cssFilter = null;
+#endif
+
+#if false
+ string[] htmlFilter = new[] {
+ "img",
+ };
+#else
+ string[] htmlFilter = null;
+#endif
+
+ var sourceHtmlFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ var sourceImageDi = new DirectoryInfo(Path.Combine(TestUtil.SourceDir.FullName, sourceHtmlFi.Name.Replace(".html", "_files")));
+
+ var destImageDi = new DirectoryInfo(Path.Combine(TestUtil.TempDir.FullName, sourceImageDi.Name));
+ var sourceCopiedToDestHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-1-Source.html")));
+ var destCssFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-2.css")));
+ var destDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-3-ConvertedByHtmlToWml.docx")));
+ var annotatedHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-4-Annotated.txt")));
+
+ if (!sourceCopiedToDestHtmlFi.Exists)
+ File.Copy(sourceHtmlFi.FullName, sourceCopiedToDestHtmlFi.FullName);
+ XElement html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceCopiedToDestHtmlFi);
+
+ string htmlString = html.ToString();
+ if (htmlFilter != null && htmlFilter.Any())
+ {
+ bool found = false;
+ foreach (var item in htmlFilter)
+ {
+ if (htmlString.Contains(item))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ sourceCopiedToDestHtmlFi.Delete();
+ return;
+ }
+ }
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+ File.WriteAllText(destCssFi.FullName, usedAuthorCss);
+
+ if (cssFilter != null && cssFilter.Any())
+ {
+ bool found = false;
+ foreach (var item in cssFilter)
+ {
+ if (usedAuthorCss.Contains(item))
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ sourceCopiedToDestHtmlFi.Delete();
+ destCssFi.Delete();
+ return;
+ }
+ }
+
+ if (sourceImageDi.Exists)
+ {
+ destImageDi.Create();
+ foreach (var file in sourceImageDi.GetFiles())
+ {
+ File.Copy(file.FullName, destImageDi.FullName + "/" + file.Name);
+ }
+ }
+
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings();
+ // image references in HTML files contain the path to the subdir that contains the images, so base URI is the name of the directory
+ // that contains the HTML files
+ settings.BaseUriForImages = Path.Combine(TestUtil.TempDir.FullName);
+
+ WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(defaultCss, usedAuthorCss, userCss, html, settings, null, s_ProduceAnnotatedHtml ? annotatedHtmlFi.FullName : null);
+ Assert.NotNull(doc);
+ if (doc != null)
+ SaveValidateAndFormatMainDocPart(destDocxFi, doc);
+
+#if DO_CONVERSION_VIA_WORD
+ var newAltChunkBeforeFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".html", "-5-AltChunkBefore.docx")));
+ var newAltChunkAfterFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, name.Replace(".html", "-6-ConvertedViaWord.docx")));
+ WordAutomationUtilities.DoConversionViaWord(newAltChunkBeforeFi, newAltChunkAfterFi, html);
+#endif
+ }
+
+ [Theory]
+ [InlineData("E0010.html")]
+ [InlineData("E0020.html")]
+ public void HW004(string name)
+ {
+
+ var sourceHtmlFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ var sourceImageDi = new DirectoryInfo(Path.Combine(TestUtil.SourceDir.FullName, sourceHtmlFi.Name.Replace(".html", "_files")));
+
+ var destImageDi = new DirectoryInfo(Path.Combine(TestUtil.TempDir.FullName, sourceImageDi.Name));
+ var sourceCopiedToDestHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-1-Source.html")));
+ var destCssFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-2.css")));
+ var destDocxFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-3-ConvertedByHtmlToWml.docx")));
+ var annotatedHtmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceHtmlFi.Name.Replace(".html", "-4-Annotated.txt")));
+
+ File.Copy(sourceHtmlFi.FullName, sourceCopiedToDestHtmlFi.FullName);
+ XElement html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceCopiedToDestHtmlFi);
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+ File.WriteAllText(destCssFi.FullName, usedAuthorCss);
+
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings();
+ settings.BaseUriForImages = Path.Combine(TestUtil.TempDir.FullName);
+
+ Assert.Throws<OpenXmlPowerToolsException>(() => HtmlToWmlConverter.ConvertHtmlToWml(defaultCss, usedAuthorCss, userCss, html, settings, null, s_ProduceAnnotatedHtml ? annotatedHtmlFi.FullName : null));
+ }
+
+ private static void SaveValidateAndFormatMainDocPart(FileInfo destDocxFi, WmlDocument doc)
+ {
+ WmlDocument formattedDoc;
+
+ doc.SaveAs(destDocxFi.FullName);
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(doc.DocumentByteArray, 0, doc.DocumentByteArray.Length);
+ using (WordprocessingDocument document = WordprocessingDocument.Open(ms, true))
+ {
+ XDocument xDoc = document.MainDocumentPart.GetXDocument();
+ document.MainDocumentPart.PutXDocumentWithFormatting();
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(document);
+ var errorsString = errors
+ .Select(e => e.Description + Environment.NewLine)
+ .StringConcatenate();
+
+ // Assert that there were no errors in the generated document.
+ Assert.Equal("", errorsString);
+ }
+ formattedDoc = new WmlDocument(destDocxFi.FullName, ms.ToArray());
+ }
+ formattedDoc.SaveAs(destDocxFi.FullName);
+ }
+
+ static string defaultCss =
+ @"html, address,
+blockquote,
+body, dd, div,
+dl, dt, fieldset, form,
+frame, frameset,
+h1, h2, h3, h4,
+h5, h6, noframes,
+ol, p, ul, center,
+dir, hr, menu, pre { display: block; unicode-bidi: embed }
+li { display: list-item }
+head { display: none }
+table { display: table }
+tr { display: table-row }
+thead { display: table-header-group }
+tbody { display: table-row-group }
+tfoot { display: table-footer-group }
+col { display: table-column }
+colgroup { display: table-column-group }
+td, th { display: table-cell }
+caption { display: table-caption }
+th { font-weight: bolder; text-align: center }
+caption { text-align: center }
+body { margin: auto; }
+h1 { font-size: 2em; margin: auto; }
+h2 { font-size: 1.5em; margin: auto; }
+h3 { font-size: 1.17em; margin: auto; }
+h4, p,
+blockquote, ul,
+fieldset, form,
+ol, dl, dir,
+menu { margin: auto }
+a { color: blue; }
+h5 { font-size: .83em; margin: auto }
+h6 { font-size: .75em; margin: auto }
+h1, h2, h3, h4,
+h5, h6, b,
+strong { font-weight: bolder }
+blockquote { margin-left: 40px; margin-right: 40px }
+i, cite, em,
+var, address { font-style: italic }
+pre, tt, code,
+kbd, samp { font-family: monospace }
+pre { white-space: pre }
+button, textarea,
+input, select { display: inline-block }
+big { font-size: 1.17em }
+small, sub, sup { font-size: .83em }
+sub { vertical-align: sub }
+sup { vertical-align: super }
+table { border-spacing: 2px; }
+thead, tbody,
+tfoot { vertical-align: middle }
+td, th, tr { vertical-align: inherit }
+s, strike, del { text-decoration: line-through }
+hr { border: 1px inset }
+ol, ul, dir,
+menu, dd { margin-left: 40px }
+ol { list-style-type: decimal }
+ol ul, ul ol,
+ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
+u, ins { text-decoration: underline }
+br:before { content: ""\A""; white-space: pre-line }
+center { text-align: center }
+:link, :visited { text-decoration: underline }
+:focus { outline: thin dotted invert }
+/* Begin bidirectionality settings (do not change) */
+BDO[DIR=""ltr""] { direction: ltr; unicode-bidi: bidi-override }
+BDO[DIR=""rtl""] { direction: rtl; unicode-bidi: bidi-override }
+*[DIR=""ltr""] { direction: ltr; unicode-bidi: embed }
+*[DIR=""rtl""] { direction: rtl; unicode-bidi: embed }
+
+";
+
+ static string userCss = @"";
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/HtmlToWmlReadAsXElement.cs b/OpenXmlPowerTools.Tests/HtmlToWmlReadAsXElement.cs
new file mode 100644
index 0000000..363878f
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/HtmlToWmlReadAsXElement.cs
@@ -0,0 +1,107 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using OpenXmlPowerTools;
+
+/*******************************************************************************************
+ * HtmlToWmlConverter expects the HTML to be passed as an XElement, i.e. as XML. While the HTML test files that
+ * are included in Open-Xml-PowerTools are able to be read as XML, most HTML is not able to be read as XML.
+ * The best solution is to use the HtmlAgilityPack, which can parse HTML and save as XML. The HtmlAgilityPack
+ * is licensed under the Ms-PL (same as Open-Xml-PowerTools) so it is convenient to include it in your solution,
+ * and thereby you can convert HTML to XML that can be processed by the HtmlToWmlConverter.
+ *
+ * A convenient way to get the DLL that has been checked out with HtmlToWmlConverter is to clone the repo at
+ * https://github.com/EricWhiteDev/HtmlAgilityPack
+ *
+ * That repo contains only the DLL that has been checked out with HtmlToWmlConverter.
+ *
+ * Of course, you can also get the HtmlAgilityPack source and compile it to get the DLL. You can find it at
+ * http://codeplex.com/HtmlAgilityPack
+ *
+ * We don't include the HtmlAgilityPack in Open-Xml-PowerTools, to simplify installation. The XUnit tests in
+ * this module do not require the HtmlAgilityPack to run.
+*******************************************************************************************/
+
+#if USE_HTMLAGILITYPACK
+using HtmlAgilityPack;
+#endif
+
+namespace OpenXmlPowerTools
+{
+ public class HtmlToWmlReadAsXElement
+ {
+ public static XElement ReadAsXElement(FileInfo sourceHtmlFi)
+ {
+ string htmlString = File.ReadAllText(sourceHtmlFi.FullName);
+ XElement html = null;
+ try
+ {
+ html = XElement.Parse(htmlString);
+ }
+#if USE_HTMLAGILITYPACK
+ catch (XmlException)
+ {
+ HtmlDocument hdoc = new HtmlDocument();
+ hdoc.Load(sourceHtmlFi.FullName, Encoding.Default);
+ hdoc.OptionOutputAsXml = true;
+ hdoc.Save(sourceHtmlFi.FullName, Encoding.Default);
+ StringBuilder sb = new StringBuilder(File.ReadAllText(sourceHtmlFi.FullName, Encoding.Default));
+ sb.Replace("&", "&");
+ sb.Replace(" ", "\xA0");
+ sb.Replace(""", "\"");
+ sb.Replace("<", "~lt;");
+ sb.Replace(">", "~gt;");
+ sb.Replace("&#", "~#");
+ sb.Replace("&", "&");
+ sb.Replace("~lt;", "<");
+ sb.Replace("~gt;", ">");
+ sb.Replace("~#", "&#");
+ File.WriteAllText(sourceHtmlFi.FullName, sb.ToString(), Encoding.Default);
+ html = XElement.Parse(sb.ToString());
+ }
+#else
+ catch (XmlException e)
+ {
+ throw e;
+ }
+#endif
+ html = (XElement)ConvertToNoNamespace(html);
+ return html;
+ }
+
+ private static object ConvertToNoNamespace(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name.LocalName,
+ element.Attributes().Where(a => !a.IsNamespaceDeclaration),
+ element.Nodes().Select(n => ConvertToNoNamespace(n)));
+ }
+ return node;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools.Tests/MarkupSimplifierTests.cs b/OpenXmlPowerTools.Tests/MarkupSimplifierTests.cs
new file mode 100644
index 0000000..3a6fcfc
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/MarkupSimplifierTests.cs
@@ -0,0 +1,160 @@
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml;
+using DocumentFormat.OpenXml.Packaging;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OpenXmlPowerTools.Tests
+{
+ public class MarkupSimplifierTests
+ {
+ private const WordprocessingDocumentType DocumentType = WordprocessingDocumentType.Document;
+
+ private const string SmartTagDocumentTextValue = "The countries include Algeria, Botswana, and Sri Lanka.";
+ private const string SmartTagDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:p >
+ <w:r>
+ <w:t xml:space=""preserve"">The countries include </w:t>
+ </w:r>
+ <w:smartTag w:uri=""urn:schemas-microsoft-com:office:smarttags"" w:element=""country-region"">
+ <w:r>
+ <w:t>Algeria</w:t>
+ </w:r>
+ </w:smartTag>
+ <w:r>
+ <w:t xml:space=""preserve"">, </w:t>
+ </w:r>
+ <w:smartTag w:uri=""urn:schemas-microsoft-com:office:smarttags"" w:element=""country-region"">
+ <w:r>
+ <w:t>Botswana</w:t>
+ </w:r>
+ </w:smartTag>
+ <w:r>
+ <w:t xml:space=""preserve"">, and </w:t>
+ </w:r>
+ <w:smartTag w:uri=""urn:schemas-microsoft-com:office:smarttags"" w:element=""country-region"">
+ <w:smartTag w:uri=""urn:schemas-microsoft-com:office:smarttags"" w:element=""place"">
+ <w:r>
+ <w:t>Sri Lanka</w:t>
+ </w:r>
+ </w:smartTag>
+ </w:smartTag>
+ <w:r>
+ <w:t>.</w:t>
+ </w:r>
+ </w:p>
+ </w:body>
+</w:document>
+";
+
+ private const string SdtDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:sdt>
+ <w:sdtPr>
+ <w:text/>
+ </w:sdtPr>
+ <w:sdtContent>
+ <w:p>
+ <w:r>
+ <w:t>Hello World!</w:t>
+ </w:r>
+ </w:p>
+ </w:sdtContent>
+ </w:sdt>
+ </w:body>
+</w:document>";
+
+ private const string GoBackBookmarkDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:p>
+ <w:bookmarkStart w:id=""0"" w:name=""_GoBack""/>
+ <w:bookmarkEnd w:id=""0""/>
+ </w:p>
+ </w:body>
+</w:document>";
+
+ [Fact]
+ public void CanRemoveSmartTags()
+ {
+ XDocument partDocument = XDocument.Parse(SmartTagDocumentXmlString);
+ Assert.True(partDocument.Descendants(W.smartTag).Any());
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ var settings = new SimplifyMarkupSettings { RemoveSmartTags = true };
+ MarkupSimplifier.SimplifyMarkup(wordDocument, settings);
+
+ partDocument = part.GetXDocument();
+ XElement t = partDocument.Descendants(W.t).First();
+
+ Assert.False(partDocument.Descendants(W.smartTag).Any());
+ Assert.Equal(SmartTagDocumentTextValue, t.Value);
+ }
+ }
+
+ [Fact]
+ public void CanRemoveContentControls()
+ {
+ XDocument partDocument = XDocument.Parse(SdtDocumentXmlString);
+ Assert.True(partDocument.Descendants(W.sdt).Any());
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ var settings = new SimplifyMarkupSettings { RemoveContentControls = true };
+ MarkupSimplifier.SimplifyMarkup(wordDocument, settings);
+
+ partDocument = part.GetXDocument();
+ XElement element = partDocument
+ .Descendants(W.body)
+ .Descendants()
+ .First();
+
+ Assert.False(partDocument.Descendants(W.sdt).Any());
+ Assert.Equal(W.p, element.Name);
+ }
+ }
+
+ [Fact]
+ public void CanRemoveGoBackBookmarks()
+ {
+ XDocument partDocument = XDocument.Parse(GoBackBookmarkDocumentXmlString);
+ Assert.Contains(partDocument
+ .Descendants(W.bookmarkStart)
+, e => e.Attribute(W.name).Value == "_GoBack" && e.Attribute(W.id).Value == "0");
+ Assert.Contains(partDocument
+ .Descendants(W.bookmarkEnd)
+, e => e.Attribute(W.id).Value == "0");
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ var settings = new SimplifyMarkupSettings { RemoveGoBackBookmark = true };
+ MarkupSimplifier.SimplifyMarkup(wordDocument, settings);
+
+ partDocument = part.GetXDocument();
+ Assert.False(partDocument.Descendants(W.bookmarkStart).Any());
+ Assert.False(partDocument.Descendants(W.bookmarkEnd).Any());
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/MetricsGetterTests.cs b/OpenXmlPowerTools.Tests/MetricsGetterTests.cs
new file mode 100644
index 0000000..ff11488
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/MetricsGetterTests.cs
@@ -0,0 +1,79 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class MgTests
+ {
+ [Theory]
+ [InlineData("Presentation.pptx")]
+ [InlineData("Spreadsheet.xlsx")]
+ [InlineData("DA001-TemplateDocument.docx")]
+ [InlineData("DA002-TemplateDocument.docx")]
+ [InlineData("DA003-Select-XPathFindsNoData.docx")]
+ [InlineData("DA004-Select-XPathFindsNoDataOptional.docx")]
+ [InlineData("DA005-SelectRowData-NoData.docx")]
+ [InlineData("DA006-SelectTestValue-NoData.docx")]
+ public void MG001(string name)
+ {
+ FileInfo fi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ MetricsGetterSettings settings = new MetricsGetterSettings()
+ {
+ IncludeTextInContentControls = false,
+ IncludeXlsxTableCellData = false,
+ RetrieveNamespaceList = true,
+ RetrieveContentTypeList = true,
+ };
+
+ var extension = fi.Extension.ToLower();
+ XElement metrics = null;
+ if (Util.IsWordprocessingML(extension))
+ {
+ WmlDocument wmlDocument = new WmlDocument(fi.FullName);
+ metrics = MetricsGetter.GetDocxMetrics(wmlDocument, settings);
+ }
+ else if (Util.IsSpreadsheetML(extension))
+ {
+ SmlDocument smlDocument = new SmlDocument(fi.FullName);
+ metrics = MetricsGetter.GetXlsxMetrics(smlDocument, settings);
+ }
+ else if (Util.IsPresentationML(extension))
+ {
+ PmlDocument pmlDocument = new PmlDocument(fi.FullName);
+ metrics = MetricsGetter.GetPptxMetrics(pmlDocument, settings);
+ }
+
+ Assert.NotNull(metrics);
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj b/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj
new file mode 100644
index 0000000..35fee27
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj
@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>net452;net46</TargetFrameworks>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
+ <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.0" />
+ <PackageReference Include="xunit" Version="2.3.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
+ <PackageReference Include="xunit.runner.console" Version="2.3.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ </ItemGroup>
+
+ <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
+ </ItemGroup>
+
+</Project>
diff --git a/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj.DotSettings b/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj.DotSettings
new file mode 100644
index 0000000..464d311
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/OpenXmlPowerTools.Tests.csproj.DotSettings
@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp40</s:String></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/OpenXmlPowerTools.Tests/OpenXmlRegexTests.cs b/OpenXmlPowerTools.Tests/OpenXmlRegexTests.cs
new file mode 100644
index 0000000..417ae62
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/OpenXmlRegexTests.cs
@@ -0,0 +1,383 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml;
+using DocumentFormat.OpenXml.Packaging;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OpenXmlPowerTools.Tests
+{
+ public class OpenXmlRegexTests
+ {
+ private const WordprocessingDocumentType DocumentType = WordprocessingDocumentType.Document;
+
+ private const string LeftDoubleQuotationMarks = @"[\u0022“„«»”]";
+ private const string Words = @"[\w\-&/]+(?:\s[\w\-&/]+)*";
+ private const string RightDoubleQuotationMarks = @"[\u0022”‟»«“]";
+
+ private const string QuotationMarksDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:p>
+ <w:r>
+ <w:t xml:space=""preserve"">Text can be enclosed in “normal double quotes” and in </w:t>
+ </w:r>
+ <w:r>
+ <w:t>«</w:t>
+ </w:r>
+ <w:r>
+ <w:t>double angle quotation marks</w:t>
+ </w:r>
+ <w:r>
+ <w:t>»</w:t>
+ </w:r>
+ <w:r>
+ <w:t>.</w:t>
+ </w:r>
+ </w:p>
+ </w:body>
+</w:document>";
+
+ private const string QuotationMarksAndTrackedChangesDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:p>
+ <w:r>
+ <w:t xml:space=""preserve"">Text can be enclosed in “normal </w:t>
+ </w:r>
+ <w:ins w:id=""8"" w:author=""Thomas Barnekow"" w:date=""2016-12-03T15:54:00Z"">
+ <w:r>
+ <w:t xml:space=""preserve"">double </w:t>
+ </w:r>
+ </w:ins>
+ <w:r>
+ <w:t xml:space=""preserve"">quotes” </w:t>
+ </w:r>
+ <w:del w:id=""9"" w:author=""Thomas Barnekow"" w:date=""2016-12-03T15:55:00Z"">
+ <w:r>
+ <w:delText xml:space=""preserve"">or </w:delText>
+ </w:r>
+ </w:del>
+ <w:ins w:id=""10"" w:author=""Thomas Barnekow"" w:date=""2016-12-03T15:55:00Z"">
+ <w:r>
+ <w:t xml:space=""preserve"">and </w:t>
+ </w:r>
+ </w:ins>
+ <w:r>
+ <w:t xml:space=""preserve"">in </w:t>
+ </w:r>
+ <w:r>
+ <w:t>«</w:t>
+ </w:r>
+ <w:r>
+ <w:t xml:space=""preserve"">double </w:t>
+ </w:r>
+ <w:ins w:id=""11"" w:author=""Thomas Barnekow"" w:date=""2016-12-03T15:54:00Z"">
+ <w:r>
+ <w:t xml:space=""preserve"">angle </w:t>
+ </w:r>
+ </w:ins>
+ <w:r>
+ <w:t>quotation marks</w:t>
+ </w:r>
+ <w:r>
+ <w:t>»</w:t>
+ </w:r>
+ <w:r>
+ <w:t>.</w:t>
+ </w:r>
+ </w:p>
+ </w:body>
+</w:document>";
+
+ private const string SymbolsAndTrackedChangesDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:p>
+ <w:r>
+ <w:t xml:space=""preserve"">We can also use symbols such as </w:t>
+ </w:r>
+ <w:del w:id=""4"" w:author=""Thomas Barnekow"" w:date=""2017-04-16T12:31:00Z"">
+ <w:r>
+ <w:sym w:font=""Wingdings"" w:char=""F028""/>
+ </w:r>
+ <w:r>
+ <w:delText xml:space=""preserve"">, </w:delText>
+ </w:r>
+ </w:del>
+ <w:r>
+ <w:sym w:font=""Wingdings"" w:char=""F021""/>
+ </w:r>
+ <w:r>
+ <w:t xml:space=""preserve""> or </w:t>
+ </w:r>
+ <w:r>
+ <w:sym w:font=""Wingdings"" w:char=""F028""/>
+ </w:r>
+ <w:r>
+ <w:t>.</w:t>
+ </w:r>
+ </w:p>
+ </w:body>
+</w:document>";
+
+ private const string FieldsDocumentXmlString =
+@"<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:body>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val=""Heading1""/>
+ </w:pPr>
+ <w:bookmarkStart w:id=""0"" w:name=""_Ref491716064""/>
+ <w:r>
+ <w:t>Article</w:t>
+ </w:r>
+ <w:bookmarkEnd w:id=""0""/>
+ </w:p>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val=""Heading2""/>
+ </w:pPr>
+ <w:bookmarkStart w:id=""1"" w:name=""_Ref491716082""/>
+ <w:r>
+ <w:t>Section</w:t>
+ </w:r>
+ <w:bookmarkEnd w:id=""1""/>
+ </w:p>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val=""HeadingBody2""/>
+ </w:pPr>
+ <w:r>
+ <w:t xml:space=""preserve"">As stated in Article </w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType=""begin""/>
+ </w:r>
+ <w:r>
+ <w:instrText xml:space=""preserve""> REF _Ref491716064 \r \h </w:instrText>
+ </w:r>
+ <w:r>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType=""separate""/>
+ </w:r>
+ <w:r>
+ <w:t>1</w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType=""end""/>
+ </w:r>
+ <w:r>
+ <w:t xml:space=""preserve""> and this Section </w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType=""begin""/>
+ </w:r>
+ <w:r>
+ <w:instrText xml:space=""preserve""> REF _Ref491716082 \r \h </w:instrText>
+ </w:r>
+ <w:r>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType=""separate""/>
+ </w:r>
+ <w:r>
+ <w:t>1.1</w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType=""end""/>
+ </w:r>
+ <w:r>
+ <w:t>, this is described in Schedule C (Performance Framework).</w:t>
+ </w:r>
+ </w:p>
+ </w:body>
+</w:document>";
+
+ private static string InnerText(XContainer e)
+ {
+ return e.Descendants(W.r)
+ .Where(r => r.Parent.Name != W.del)
+ .Select(UnicodeMapper.RunToString)
+ .StringConcatenate();
+ }
+
+ private static string InnerDelText(XContainer e)
+ {
+ return e.Descendants(W.delText)
+ .Select(delText => delText.Value)
+ .StringConcatenate();
+ }
+
+ [Fact]
+ public void CanReplaceTextWithQuotationMarks()
+ {
+ XDocument partDocument = XDocument.Parse(QuotationMarksDocumentXmlString);
+ XElement p = partDocument.Descendants(W.p).First();
+ string innerText = InnerText(p);
+
+ Assert.Equal(
+ "Text can be enclosed in “normal double quotes” and in «double angle quotation marks».",
+ innerText);
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ IEnumerable<XElement> content = partDocument.Descendants(W.p);
+ var regex = new Regex(string.Format("{0}(?<words>{1}){2}", LeftDoubleQuotationMarks, Words,
+ RightDoubleQuotationMarks));
+ int count = OpenXmlRegex.Replace(content, regex, "‘changed ${words}’", null);
+
+ p = partDocument.Descendants(W.p).First();
+ innerText = InnerText(p);
+
+ Assert.Equal(2, count);
+ Assert.Equal(
+ "Text can be enclosed in ‘changed normal double quotes’ and in ‘changed double angle quotation marks’.",
+ innerText);
+ }
+ }
+
+ [Fact]
+ public void CanReplaceTextWithQuotationMarksAndAddTrackedChangesWhenReplacing()
+ {
+ XDocument partDocument = XDocument.Parse(QuotationMarksDocumentXmlString);
+ XElement p = partDocument.Descendants(W.p).First();
+ string innerText = InnerText(p);
+
+ Assert.Equal(
+ "Text can be enclosed in “normal double quotes” and in «double angle quotation marks».",
+ innerText);
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ IEnumerable<XElement> content = partDocument.Descendants(W.p);
+ var regex = new Regex(string.Format("{0}(?<words>{1}){2}", LeftDoubleQuotationMarks, Words,
+ RightDoubleQuotationMarks));
+ int count = OpenXmlRegex.Replace(content, regex, "‘changed ${words}’", null, true, "John Doe");
+
+ p = partDocument.Descendants(W.p).First();
+ innerText = InnerText(p);
+
+ Assert.Equal(2, count);
+ Assert.Equal(
+ "Text can be enclosed in ‘changed normal double quotes’ and in ‘changed double angle quotation marks’.",
+ innerText);
+
+ Assert.Contains(p.Elements(W.ins), e => InnerText(e) == "‘changed normal double quotes’");
+ Assert.Contains(p.Elements(W.ins), e => InnerText(e) == "‘changed double angle quotation marks’");
+
+ Assert.Contains(p.Elements(W.del), e => InnerDelText(e) == "“normal double quotes”");
+ Assert.Contains(p.Elements(W.del), e => InnerDelText(e) == "«double angle quotation marks»");
+ }
+ }
+
+ [Fact]
+ public void CanReplaceTextWithQuotationMarksAndTrackedChanges()
+ {
+ XDocument partDocument = XDocument.Parse(QuotationMarksAndTrackedChangesDocumentXmlString);
+ XElement p = partDocument.Descendants(W.p).First();
+ string innerText = InnerText(p);
+
+ Assert.Equal(
+ "Text can be enclosed in “normal double quotes” and in «double angle quotation marks».",
+ innerText);
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ IEnumerable<XElement> content = partDocument.Descendants(W.p);
+ var regex = new Regex(string.Format("{0}(?<words>{1}){2}", LeftDoubleQuotationMarks, Words,
+ RightDoubleQuotationMarks));
+ int count = OpenXmlRegex.Replace(content, regex, "‘changed ${words}’", null, true, "John Doe");
+
+ p = partDocument.Descendants(W.p).First();
+ innerText = InnerText(p);
+
+ Assert.Equal(2, count);
+ Assert.Equal(
+ "Text can be enclosed in ‘changed normal double quotes’ and in ‘changed double angle quotation marks’.",
+ innerText);
+
+ Assert.Contains(p.Elements(W.ins), e => InnerText(e) == "‘changed normal double quotes’");
+ Assert.Contains(p.Elements(W.ins), e => InnerText(e) == "‘changed double angle quotation marks’");
+ }
+ }
+
+ [Fact]
+ public void CanReplaceTextWithSymbolsAndTrackedChanges()
+ {
+ XDocument partDocument = XDocument.Parse(SymbolsAndTrackedChangesDocumentXmlString);
+ XElement p = partDocument.Descendants(W.p).First();
+ string innerText = InnerText(p);
+
+ Assert.Equal("We can also use symbols such as \uF021 or \uF028.", innerText);
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ IEnumerable<XElement> content = partDocument.Descendants(W.p);
+ var regex = new Regex(@"[\uF021]");
+ int count = OpenXmlRegex.Replace(content, regex, "\uF028", null, true, "John Doe");
+
+ p = partDocument.Descendants(W.p).First();
+ innerText = InnerText(p);
+
+ Assert.Equal(1, count);
+ Assert.Equal("We can also use symbols such as \uF028 or \uF028.", innerText);
+
+ Assert.Contains(p.Descendants(W.ins), ins => ins.Descendants(W.sym).Any(
+ sym => sym.Attribute(W.font).Value == "Wingdings" &&
+ sym.Attribute(W._char).Value == "F028"));
+ }
+ }
+
+ [Fact]
+ public void CanReplaceTextWithFields()
+ {
+ XDocument partDocument = XDocument.Parse(FieldsDocumentXmlString);
+ XElement p = partDocument.Descendants(W.p).Last();
+ string innerText = InnerText(p);
+
+ Assert.Equal("As stated in Article {__1} and this Section {__1.1}, this is described in Schedule C (Performance Framework).",
+ innerText);
+
+ using (var stream = new MemoryStream())
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.PutXDocument(partDocument);
+
+ IEnumerable<XElement> content = partDocument.Descendants(W.p);
+ var regex = new Regex(@"Schedule C \(Performance Framework\)");
+ int count = OpenXmlRegex.Replace(content, regex, "Exhibit 4", null, true, "John Doe");
+
+ p = partDocument.Descendants(W.p).Last();
+ innerText = InnerText(p);
+
+ Assert.Equal(1, count);
+ Assert.Equal("As stated in Article {__1} and this Section {__1.1}, this is described in Exhibit 4.", innerText);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/PowerToolsBlockExtensionsTests.cs b/OpenXmlPowerTools.Tests/PowerToolsBlockExtensionsTests.cs
new file mode 100644
index 0000000..a0ffb42
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/PowerToolsBlockExtensionsTests.cs
@@ -0,0 +1,145 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OpenXmlPowerTools.Tests
+{
+ public class PowerToolsBlockExtensionsTests : TestsBase
+ {
+ [Fact]
+ public void MustBeginPowerToolsBlockToUsePowerTools()
+ {
+ using (var stream = new MemoryStream())
+ {
+ CreateEmptyWordprocessingDocument(stream);
+
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
+ {
+ MainDocumentPart part = wordDocument.MainDocumentPart;
+
+ // Add a first paragraph through the SDK.
+ Body body = part.Document.Body;
+ body.AppendChild(new Paragraph(new Run(new Text("First"))));
+
+ // This demonstrates the usage of the BeginPowerToolsBlock method to
+ // demarcate blocks or regions of code where the PowerTools are used
+ // in between usages of the strongly typed classes.
+ wordDocument.BeginPowerToolsBlock();
+
+ // Get content through the PowerTools. We will see the one paragraph added
+ // by using the strongly typed SDK classes.
+ XDocument content = part.GetXDocument();
+ List<XElement> paragraphElements = content.Descendants(W.p).ToList();
+ Assert.Single(paragraphElements);
+ Assert.Equal("First", paragraphElements[0].Value);
+
+ // This demonstrates the usage of the EndPowerToolsBlock method to
+ // demarcate blocks or regions of code where the PowerTools are used
+ // in between usages of the strongly typed classes.
+ wordDocument.EndPowerToolsBlock();
+
+ // Add a second paragraph through the SDK in the exact same way as above.
+ body = part.Document.Body;
+ body.AppendChild(new Paragraph(new Run(new Text("Second"))));
+ part.Document.Save();
+
+ // Get content through the PowerTools in the exact same way as above,
+ // noting that we have not used the BeginPowerToolsBlock method to
+ // mark the beginning of the next PowerTools Block.
+ // What we will see in this case is that we still only get the first
+ // paragraph. This is caused by the GetXDocument method using the cached
+ // XDocument, i.e., the annotation, rather reading the part's stream again.
+ content = part.GetXDocument();
+ paragraphElements = content.Descendants(W.p).ToList();
+ Assert.Single(paragraphElements);
+ Assert.Equal("First", paragraphElements[0].Value);
+
+ // To make the GetXDocument read the parts' streams, we need to begin
+ // the next PowerTools Block. This will remove the annotations from the
+ // parts and make the PowerTools read the part's stream instead of
+ // using the outdated annotation.
+ wordDocument.BeginPowerToolsBlock();
+
+ // Get content through the PowerTools in the exact same way as above.
+ // We should now see both paragraphs.
+ content = part.GetXDocument();
+ paragraphElements = content.Descendants(W.p).ToList();
+ Assert.Equal(2, paragraphElements.Count);
+ Assert.Equal("First", paragraphElements[0].Value);
+ Assert.Equal("Second", paragraphElements[1].Value);
+ }
+ }
+ }
+
+ [Fact]
+ public void MustEndPowerToolsBlockToUseStronglyTypedClasses()
+ {
+ using (var stream = new MemoryStream())
+ {
+ CreateEmptyWordprocessingDocument(stream);
+
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
+ {
+ MainDocumentPart part = wordDocument.MainDocumentPart;
+
+ // Add a paragraph through the SDK.
+ Body body = part.Document.Body;
+ body.AppendChild(new Paragraph(new Run(new Text("Added through SDK"))));
+
+ // Begin the PowerTools Block, which saves any changes made through the strongly
+ // typed SDK classes to the parts of the WordprocessingDocument.
+ // In this case, this could also be done by invoking the Save method on the
+ // WordprocessingDocument, which will save all parts that had changes, or by
+ // invoking part.RootElement.Save() for the one part that was changed.
+ wordDocument.BeginPowerToolsBlock();
+
+ // Add a paragraph through the PowerTools.
+ XDocument content = part.GetXDocument();
+ XElement bodyElement = content.Descendants(W.body).First();
+ bodyElement.Add(new XElement(W.p, new XElement(W.r, new XElement(W.t, "Added through PowerTools"))));
+ part.PutXDocument();
+
+ // Get the part's content through the SDK. However, we will only see what we
+ // added through the SDK, not what we added through the PowerTools functionality.
+ body = part.Document.Body;
+ List<Paragraph> paragraphs = body.Elements<Paragraph>().ToList();
+ Assert.Single(paragraphs);
+ Assert.Equal("Added through SDK", paragraphs[0].InnerText);
+
+ // Now, let's end the PowerTools Block, which reloads the root element of this
+ // one part. Reloading those root elements this way is fine if you know exactly
+ // which parts had their content changed by the Open XML PowerTools.
+ wordDocument.EndPowerToolsBlock();
+
+ // Get the part's content through the SDK. Having reloaded the root element,
+ // we should now see both paragraphs.
+ body = part.Document.Body;
+ paragraphs = body.Elements<Paragraph>().ToList();
+ Assert.Equal(2, paragraphs.Count);
+ Assert.Equal("Added through SDK", paragraphs[0].InnerText);
+ Assert.Equal("Added through PowerTools", paragraphs[1].InnerText);
+ }
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/PowerToolsBlockTests.cs b/OpenXmlPowerTools.Tests/PowerToolsBlockTests.cs
new file mode 100644
index 0000000..a08041f
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/PowerToolsBlockTests.cs
@@ -0,0 +1,78 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OpenXmlPowerTools.Tests
+{
+ public class PowerToolsBlockTests : TestsBase
+ {
+ [Fact]
+ public void CanUsePowerToolsBlockToDemarcateApis()
+ {
+ using (var stream = new MemoryStream())
+ {
+ CreateEmptyWordprocessingDocument(stream);
+
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
+ {
+ MainDocumentPart part = wordDocument.MainDocumentPart;
+
+ // Add a paragraph through the SDK.
+ Body body = part.Document.Body;
+ body.AppendChild(new Paragraph(new Run(new Text("Added through SDK"))));
+
+ // This demonstrates the use of the PowerToolsBlock in a using statement to
+ // demarcate the intermittent use of the PowerTools.
+ using (new PowerToolsBlock(wordDocument))
+ {
+ // Assert that we can see the paragraph added through the strongly typed classes.
+ XDocument content = part.GetXDocument();
+ List<XElement> paragraphElements = content.Descendants(W.p).ToList();
+ Assert.Single(paragraphElements);
+ Assert.Equal("Added through SDK", paragraphElements[0].Value);
+
+ // Add a paragraph through the PowerTools.
+ XElement bodyElement = content.Descendants(W.body).First();
+ bodyElement.Add(new XElement(W.p, new XElement(W.r, new XElement(W.t, "Added through PowerTools"))));
+ part.PutXDocument();
+ }
+
+ // Get the part's content through the SDK. Having used the PowerToolsBlock,
+ // we should see both paragraphs.
+ body = part.Document.Body;
+ List<Paragraph> paragraphs = body.Elements<Paragraph>().ToList();
+ Assert.Equal(2, paragraphs.Count);
+ Assert.Equal("Added through SDK", paragraphs[0].InnerText);
+ Assert.Equal("Added through PowerTools", paragraphs[1].InnerText);
+ }
+ }
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenPassingNull()
+ {
+ Assert.Throws<ArgumentNullException>(() => new PowerToolsBlock(null));
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/PresentationBuilderTests.cs b/OpenXmlPowerTools.Tests/PresentationBuilderTests.cs
new file mode 100644
index 0000000..120e31f
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/PresentationBuilderTests.cs
@@ -0,0 +1,162 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class PbTests
+ {
+ [Fact]
+ public void PB001_Formatting()
+ {
+ string name1 = "PB001-Input1.pptx";
+ string name2 = "PB001-Input2.pptx";
+ FileInfo source1Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ List<SlideSource> sources = null;
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source1Pptx.FullName), 1, true),
+ new SlideSource(new PmlDocument(source2Pptx.FullName), 0, true),
+ };
+ var processedDestPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "PB001-Formatting.pptx"));
+ PresentationBuilder.BuildPresentation(sources, processedDestPptx.FullName);
+ }
+
+ [Fact]
+ public void PB002_Formatting()
+ {
+ string name2 = "PB001-Input2.pptx";
+ FileInfo source2Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ List<SlideSource> sources = null;
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source2Pptx.FullName), 0, true),
+ };
+ var processedDestPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "PB002-Formatting.pptx"));
+ PresentationBuilder.BuildPresentation(sources, processedDestPptx.FullName);
+ }
+
+ [Fact]
+ public void PB003_Formatting()
+ {
+ string name1 = "PB001-Input1.pptx";
+ string name2 = "PB001-Input3.pptx";
+ FileInfo source1Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ List<SlideSource> sources = null;
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source1Pptx.FullName), 1, true),
+ new SlideSource(new PmlDocument(source2Pptx.FullName), 0, true),
+ };
+ var processedDestPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "PB003-Formatting.pptx"));
+ PresentationBuilder.BuildPresentation(sources, processedDestPptx.FullName);
+ }
+
+ [Fact]
+ public void PB004_Formatting()
+ {
+ string name1 = "PB001-Input1.pptx";
+ string name2 = "PB001-Input3.pptx";
+ FileInfo source1Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ List<SlideSource> sources = null;
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source2Pptx.FullName), 0, true),
+ new SlideSource(new PmlDocument(source1Pptx.FullName), 1, true),
+ };
+ var processedDestPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "PB004-Formatting.pptx"));
+ PresentationBuilder.BuildPresentation(sources, processedDestPptx.FullName);
+ }
+
+ [Fact]
+ public void PB005_Formatting()
+ {
+ string name1 = "PB001-Input1.pptx";
+ string name2 = "PB001-Input3.pptx";
+ FileInfo source1Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Pptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ List<SlideSource> sources = null;
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source2Pptx.FullName), 0, 0, true),
+ new SlideSource(new PmlDocument(source1Pptx.FullName), 1, true),
+ new SlideSource(new PmlDocument(source2Pptx.FullName), 0, true),
+ };
+ var processedDestPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "PB005-Formatting.pptx"));
+ PresentationBuilder.BuildPresentation(sources, processedDestPptx.FullName);
+ }
+
+ [Fact]
+ public void PB006_VideoFormats()
+ {
+ // This presentation contains videos with content types video/mp4, video/quicktime, video/unknown, video/x-ms-asf, and video/x-msvideo.
+ FileInfo sourcePptx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, "PP006-Videos.pptx"));
+
+ var oldMediaDataContentTypes = GetMediaDataContentTypes(sourcePptx);
+
+ List<SlideSource> sources = null;
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(sourcePptx.FullName), true),
+ };
+ var processedDestPptx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "PB006-Videos.pptx"));
+ PresentationBuilder.BuildPresentation(sources, processedDestPptx.FullName);
+
+ var newMediaDataContentTypes = GetMediaDataContentTypes(processedDestPptx);
+
+ Assert.Equal(oldMediaDataContentTypes, newMediaDataContentTypes);
+ }
+
+ private static string[] GetMediaDataContentTypes(FileInfo fi)
+ {
+ using (PresentationDocument ptDoc = PresentationDocument.Open(fi.FullName, false))
+ {
+ return ptDoc.PresentationPart.SlideParts.SelectMany(
+ p => p.DataPartReferenceRelationships.Select(d => d.DataPart.ContentType))
+ .Distinct()
+ .OrderBy(m => m)
+ .ToArray();
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/PtUtilTests.cs b/OpenXmlPowerTools.Tests/PtUtilTests.cs
new file mode 100644
index 0000000..e43f84f
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/PtUtilTests.cs
@@ -0,0 +1,54 @@
+/***************************************************************************
+
+Copyright (c) Eric White 2018.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class PtUtilTests
+ {
+ [Theory(Skip = "This is failing on AppVeyor")]
+ [InlineData("PU/PU001-Test001.mht")]
+ public void PU001(string name)
+ {
+ FileInfo sourceMht = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ var src = File.ReadAllText(sourceMht.FullName);
+ var p = MhtParser.Parse(src);
+ Assert.True(p.ContentType != null);
+ Assert.True(p.MimeVersion != null);
+ Assert.True(p.Parts.Length != 0);
+ Assert.DoesNotContain(p.Parts, part => part.ContentType == null || part.ContentLocation == null);
+ }
+
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/RevisionAccepterTests.cs b/OpenXmlPowerTools.Tests/RevisionAccepterTests.cs
new file mode 100644
index 0000000..d13ae13
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/RevisionAccepterTests.cs
@@ -0,0 +1,55 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class RaTests
+ {
+ [Theory]
+ [InlineData("RA001-Tracked-Revisions-01.docx")]
+ [InlineData("RA001-Tracked-Revisions-02.docx")]
+
+ public void RA001(string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ WmlDocument notAccepted = new WmlDocument(sourceDocx.FullName);
+ WmlDocument afterAccepting = RevisionAccepter.AcceptRevisions(notAccepted);
+ var processedDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-processed-by-RevisionAccepter.docx")));
+ afterAccepting.SaveAs(processedDestDocx.FullName);
+ }
+
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/RevisionProcessorTests.cs b/OpenXmlPowerTools.Tests/RevisionProcessorTests.cs
new file mode 100644
index 0000000..9835375
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/RevisionProcessorTests.cs
@@ -0,0 +1,224 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class RpTests
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // perf settings
+ public static bool m_CopySourceFilesToTempDir = true;
+ public static bool m_OpenTempDirInExplorer = false;
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ [Theory]
+ //[InlineData("RP/RP001-Tracked-Revisions-01.docx")]
+ //[InlineData("RP/RP001-Tracked-Revisions-02.docx")]
+ [InlineData("RP/RP002-Deleted-Text.docx")]
+ [InlineData("RP/RP003-Inserted-Text.docx")]
+ [InlineData("RP/RP004-Deleted-Text-in-CC.docx")]
+ [InlineData("RP/RP005-Deleted-Paragraph-Mark.docx")]
+ [InlineData("RP/RP006-Inserted-Paragraph-Mark.docx")]
+ [InlineData("RP/RP007-Multiple-Deleted-Para-Mark.docx")]
+ [InlineData("RP/RP008-Multiple-Inserted-Para-Mark.docx")]
+ [InlineData("RP/RP009-Deleted-Table-Row.docx")]
+ [InlineData("RP/RP010-Inserted-Table-Row.docx")]
+ [InlineData("RP/RP011-Multiple-Deleted-Rows.docx")]
+ [InlineData("RP/RP012-Multiple-Inserted-Rows.docx")]
+ [InlineData("RP/RP013-Deleted-Math-Control-Char.docx")]
+ [InlineData("RP/RP014-Inserted-Math-Control-Char.docx")]
+ [InlineData("RP/RP015-MoveFrom-MoveTo.docx")]
+ [InlineData("RP/RP016-Deleted-CC.docx")]
+ [InlineData("RP/RP017-Inserted-CC.docx")]
+ [InlineData("RP/RP018-MoveFrom-MoveTo-CC.docx")]
+ [InlineData("RP/RP019-Deleted-Field-Code.docx")]
+ [InlineData("RP/RP020-Inserted-Field-Code.docx")]
+ [InlineData("RP/RP021-Inserted-Numbering-Properties.docx")]
+ [InlineData("RP/RP022-NumberingChange.docx")]
+ [InlineData("RP/RP023-NumberingChange.docx")]
+ [InlineData("RP/RP024-ParagraphMark-rPr-Change.docx")]
+ [InlineData("RP/RP025-Paragraph-Props-Change.docx")]
+ [InlineData("RP/RP026-NumberingChange.docx")]
+ [InlineData("RP/RP027-Change-Section.docx")]
+ [InlineData("RP/RP028-Table-Grid-Change.docx")]
+ [InlineData("RP/RP029-Table-Row-Props-Change.docx")]
+ [InlineData("RP/RP030-Table-Row-Props-Change.docx")]
+ [InlineData("RP/RP031-Table-Prop-Change.docx")]
+ [InlineData("RP/RP032-Table-Prop-Change.docx")]
+ [InlineData("RP/RP033-Table-Prop-Ex-Change.docx")]
+ [InlineData("RP/RP034-Deleted-Cells.docx")]
+ [InlineData("RP/RP035-Inserted-Cells.docx")]
+ [InlineData("RP/RP036-Vert-Merged-Cells.docx")]
+ [InlineData("RP/RP037-Changed-Style-Para-Props.docx")]
+ [InlineData("RP/RP038-Inserted-Paras-at-End.docx")]
+ [InlineData("RP/RP039-Inserted-Paras-at-End.docx")]
+ [InlineData("RP/RP040-Deleted-Paras-at-End.docx")]
+ [InlineData("RP/RP041-Cell-With-Empty-Paras-at-End.docx")]
+ [InlineData("RP/RP042-Deleted-Para-Mark-at-End.docx")]
+ [InlineData("RP/RP043-MERGEFORMAT-Field-Code.docx")]
+ [InlineData("RP/RP044-MERGEFORMAT-Field-Code.docx")]
+ [InlineData("RP/RP045-One-and-Half-Deleted-Lines-at-End.docx")]
+ [InlineData("RP/RP046-Consecutive-Deleted-Ranges.docx")]
+ [InlineData("RP/RP047-Inserted-and-Deleted-Paragraph-Mark.docx")]
+ [InlineData("RP/RP048-Deleted-Inserted-Para-Mark.docx")]
+ [InlineData("RP/RP049-Deleted-Para-Before-Table.docx")]
+ [InlineData("RP/RP050-Deleted-Footnote.docx")]
+ [InlineData("RP/RP052-Deleted-Para-Mark.docx")]
+ public void RP001(string name)
+ {
+ var sourceFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ var baselineAcceptedFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name.Replace(".docx", "-Accepted.docx")));
+ var baselineRejectedFi = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name.Replace(".docx", "-Rejected.docx")));
+
+ WmlDocument sourceWml = new WmlDocument(sourceFi.FullName);
+ WmlDocument afterRejectingWml = RevisionProcessor.RejectRevisions(sourceWml);
+ WmlDocument afterAcceptingWml = RevisionProcessor.AcceptRevisions(sourceWml);
+
+ var processedAcceptedFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceFi.Name.Replace(".docx", "-Accepted.docx")));
+ afterAcceptingWml.SaveAs(processedAcceptedFi.FullName);
+
+ var processedRejectedFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceFi.Name.Replace(".docx", "-Rejected.docx")));
+ afterRejectingWml.SaveAs(processedRejectedFi.FullName);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Copy source files to temp dir
+ if (m_CopySourceFilesToTempDir)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var sourceDocxCopiedToDestFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceFi.Name));
+ if (!sourceDocxCopiedToDestFi.Exists)
+ sourceWml.SaveAs(sourceDocxCopiedToDestFi.FullName);
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // create batch file to copy properly processed documents to the TestFiles directory.
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var batchFileName = "Copy-Gen-Files-To-TestFiles.bat";
+ var batchFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, batchFileName));
+ var batch = "";
+ batch += "copy " + processedAcceptedFi.FullName + " " + baselineAcceptedFi.FullName + Environment.NewLine;
+ batch += "copy " + processedRejectedFi.FullName + " " + baselineRejectedFi.FullName + Environment.NewLine;
+ if (batchFi.Exists)
+ File.AppendAllText(batchFi.FullName, batch);
+ else
+ File.WriteAllText(batchFi.FullName, batch);
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Open Windows Explorer
+ if (m_OpenTempDirInExplorer)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var semaphorFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "z_ExplorerOpenedSemaphore.txt"));
+ if (!semaphorFi.Exists)
+ {
+ File.WriteAllText(semaphorFi.FullName, "");
+ TestUtil.Explorer(TestUtil.TempDir);
+ }
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Use WmlComparer to see if accepted baseline is same as processed
+ if (baselineAcceptedFi.Exists)
+ {
+ var baselineAcceptedWml = new WmlDocument(baselineAcceptedFi.FullName);
+ WmlComparerSettings wmlComparerSettings = new WmlComparerSettings();
+ WmlDocument result = WmlComparer.Compare(baselineAcceptedWml, afterAcceptingWml, wmlComparerSettings);
+ var revisions = WmlComparer.GetRevisions(result, wmlComparerSettings);
+ if (revisions.Any())
+ {
+ Assert.True(false, "Regression Error: Accepted baseline document did not match processed document");
+ }
+ }
+ else
+ {
+ Assert.True(false, "No Accepted baseline document");
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Use WmlComparer to see if rejected baseline is same as processed
+ if (baselineRejectedFi.Exists)
+ {
+ var baselineRejectedWml = new WmlDocument(baselineRejectedFi.FullName);
+ WmlComparerSettings wmlComparerSettings = new WmlComparerSettings();
+ WmlDocument result = WmlComparer.Compare(baselineRejectedWml, afterRejectingWml, wmlComparerSettings);
+ var revisions = WmlComparer.GetRevisions(result, wmlComparerSettings);
+ if (revisions.Any())
+ {
+ Assert.True(false, "Regression Error: Rejected baseline document did not match processed document");
+ }
+ }
+ else
+ {
+ Assert.True(false, "No Rejected baseline document");
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/SmlCellFormatterTests.cs b/OpenXmlPowerTools.Tests/SmlCellFormatterTests.cs
new file mode 100644
index 0000000..489ae19
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/SmlCellFormatterTests.cs
@@ -0,0 +1,167 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class CfTests
+ {
+ [Theory]
+ [InlineData("General", "0", "0", null)]
+ [InlineData("0", "1.1000000000000001", "1", null)]
+ [InlineData("0", "10.1", "10", null)]
+ [InlineData("0", "100.1", "100", null)]
+ [InlineData("0", "100000000.09999999", "100000000", null)]
+ [InlineData("0.00", "1.1000000000000001", "1.10", null)]
+ [InlineData("0.00", "10.1", "10.10", null)]
+ [InlineData("0.00", "100000000.09999999", "100000000.10", null)]
+ [InlineData("#,##0", "1.1000000000000001", "1", null)]
+ [InlineData("#,##0", "10.1", "10", null)]
+ [InlineData("#,##0", "100000000.09999999", "100,000,000", null)]
+ [InlineData("#,##0", "1000000000.1", "1,000,000,000", null)]
+ [InlineData("#,##0.00", "1.1000000000000001", "1.10", null)]
+ [InlineData("#,##0.00", "10.1", "10.10", null)]
+ [InlineData("#,##0.00", "1000.1", "1,000.10", null)]
+ [InlineData("#,##0.00", "100000000.09999999", "100,000,000.10", null)]
+ [InlineData("0%", "0.01", "1%", null)]
+ [InlineData("0%", "0.25", "25%", null)]
+ [InlineData("0%", "1", "100%", null)]
+ [InlineData("0%", "2", "200%", null)]
+ [InlineData("0%", "0.1", "10%", null)]
+ [InlineData("0.00%", "0.01", "1.00%", null)]
+ [InlineData("0.00%", "0.25", "25.00%", null)]
+ [InlineData("0.00%", "1", "100.00%", null)]
+ [InlineData("0.00%", "2", "200.00%", null)]
+ [InlineData("0.00%", "0.1", "10.00%", null)]
+ [InlineData("0.00%", "0.1025", "10.25%", null)]
+ [InlineData("0.00E+00", "0.01", "1.00E-02", null)]
+ [InlineData("0.00E+00", "0.25", "2.50E-01", null)]
+ [InlineData("0.00E+00", "1", "1.00E+00", null)]
+ [InlineData("0.00E+00", "100", "1.00E+02", null)]
+ [InlineData("0.00E+00", "1000", "1.00E+03", null)]
+ [InlineData("0.00E+00", "10000.1", "1.00E+04", null)]
+ [InlineData("0.00E+00", "100000.5", "1.00E+05", null)]
+ [InlineData("0.00E+00", "0.1", "1.00E-01", null)]
+ [InlineData("# ?/?", "0.125", "0.13", null)]
+ [InlineData("# ?/?", "0.25", "0.25", null)]
+ [InlineData("# ??/??", "0.125", "0.13", null)]
+ [InlineData("# ??/??", "0.25", "0.25", null)]
+ [InlineData("mm-dd-yy", "42344", "12-06-15", null)]
+ [InlineData("d-mmm-yy", "42344", "6-Dec-15", null)]
+ [InlineData("d-mmm", "42344", "6-Dec", null)]
+ [InlineData("mmm-yy", "42344", "Dec-15", null)]
+ [InlineData("h:mm AM/PM", "42344.295138888891", "7:05 AM", null)]
+ [InlineData("h:mm:ss AM/PM", "42344.295405092591", "7:05:23 AM", null)]
+ [InlineData("h:mm", "42344.295405092591", "7:05", null)]
+ [InlineData("h:mm:ss", "42344.295405092591", "7:05:23", null)]
+ [InlineData("m/d/yy h:mm", "42344.295405092591", "12/6/15 7:05", null)]
+ [InlineData("#,##0 ;(#,##0)", "100", "100", null)]
+ [InlineData("#,##0 ;(#,##0)", "-100", "(100)", null)]
+ [InlineData("#,##0 ;[Red](#,##0)", "100", "100", null)]
+ [InlineData("#,##0 ;[Red](#,##0)", "-100", "(100)", "Red")]
+ [InlineData("#,##0.00;(#,##0.00)", "100.00", "100.00", null)]
+ [InlineData("#,##0.00;(#,##0.00)", "-100.00", "(100.00)", null)]
+ [InlineData("#,##0.00;[Red](#,##0.00)", "100.00", "100.00", null)]
+ [InlineData("#,##0.00;[Red](#,##0.00)", "-100.00", "(100.00)", "Red")]
+ [InlineData("mm:ss", "42344.295405092591", "05:23", null)]
+ [InlineData("[h]:mm:ss", "42344.295405092591", "1016263:05:23", null)]
+ [InlineData("mm:ss.0", "42344.295445092591", "05:26:456", null)]
+ [InlineData("##0.0E+0", "100.0", "100.0E+0", null)]
+ [InlineData("##0.0E+0", "543.210", "543.2E+0", null)]
+
+ public void CF001(string formatCode, string value, string expected, string expectedColor)
+ {
+ string color;
+ string r = SmlCellFormatter.FormatCell(formatCode, value, out color);
+ Assert.Equal(expected, r);
+ Assert.Equal(expectedColor, color);
+ }
+
+ [Theory]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "A1:A1", "$123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "A2:A2", "-$123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "A3:A3", "$0.00", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "B1:B1", "$ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "B2:B2", "$ (123.45)", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "B3:B3", "$ -", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "C1:C1", "£ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "C2:C2", "-£ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "C3:C3", "£ -", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "D1:D1", "€ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "D2:D2", "€ (123.45)", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "D3:D3", "€ -", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "E1:E1", "¥ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "E2:E2", "¥ -123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "E3:E3", "¥ -", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "F1:F1", "CHF 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "F2:F2", "CHF -123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "F3:F3", "CHF -", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "G1:G1", "₩ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "G2:G2", "-₩ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "G3:G3", "₩ -", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "H1:H1", "£ 123.45", null)]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "H2:H2", "-£ 123.45", "Red")]
+ [InlineData("SH151-Custom-Cell-Format-Currency.xlsx", "Sheet1", "H3:H3", "£ -", null)]
+
+ [InlineData("SH152-Custom-Cell-Format.xlsx", "Sheet1", "A1:A1", "1,234,567.0000", null)]
+ [InlineData("SH152-Custom-Cell-Format.xlsx", "Sheet1", "B1:B1", "This is the value: abc", null)]
+
+ [InlineData("SH201-Cell-C1-Without-R-Attr.xlsx", "Sheet1", "C1:C1", "3", null)]
+ [InlineData("SH202-Cell-C1-D1-Without-R-Attr.xlsx", "Sheet1", "C1:C1", "3", null)]
+ [InlineData("SH203-Cell-C1-D1-E1-Without-R-Attr.xlsx", "Sheet1", "C1:C1", "3", null)]
+ [InlineData("SH204-Cell-A1-B1-C1-Without-R-Attr.xlsx", "Sheet1", "A1:A1", "1", null)]
+
+ public void CF002(string name, string sheetName, string range, string expected, string expectedColor)
+ {
+ FileInfo sourceXlsx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestXlsx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", "-1-Source.xlsx")));
+ if (!sourceCopiedToDestXlsx.Exists)
+ File.Copy(sourceXlsx.FullName, sourceCopiedToDestXlsx.FullName);
+
+ var dataTemplateFileNameSuffix = string.Format("-2-Generated-XmlData-{0}.xml", range.Replace(":", ""));
+ var dataXmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", dataTemplateFileNameSuffix)));
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(sourceXlsx.FullName, true))
+ {
+ var rangeXml = SmlDataRetriever.RetrieveRange(sDoc, sheetName, range);
+ string displayValue = (string)rangeXml.Descendants("DisplayValue").FirstOrDefault();
+ string displayColor = (string)rangeXml.Descendants("DisplayColor").FirstOrDefault();
+ Assert.Equal(expected, displayValue);
+ Assert.Equal(expectedColor, displayColor);
+ }
+ }
+
+
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/SmlToHtmlConverterTests.cs b/OpenXmlPowerTools.Tests/SmlToHtmlConverterTests.cs
new file mode 100644
index 0000000..08d4b45
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/SmlToHtmlConverterTests.cs
@@ -0,0 +1,205 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class ShTests
+ {
+ // PowerShell oneliner that generates InlineData for all files in a directory
+ // dir | % { '[InlineData("' + $_.Name + '")]' } | clip
+
+ [Theory]
+ [InlineData("SH101-SimpleFormats.xlsx", "Sheet1")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1")]
+ [InlineData("SH103-No-SharedString.xlsx", "Sheet1")]
+ [InlineData("SH104-With-SharedString.xlsx", "Sheet1")]
+ [InlineData("SH105-No-SharedString.xlsx", "Sheet1")]
+ [InlineData("SH106-9-x-9-Formatted.xlsx", "Sheet1")]
+ [InlineData("SH108-SimpleFormattedCell.xlsx", "Sheet1")]
+ [InlineData("SH109-CellWithBorder.xlsx", "Sheet1")]
+ [InlineData("SH110-CellWithMasterStyle.xlsx", "Sheet1")]
+ [InlineData("SH111-ChangedDefaultColumnWidth.xlsx", "Sheet1")]
+ [InlineData("SH112-NotVertMergedCell.xlsx", "Sheet1")]
+ [InlineData("SH113-VertMergedCell.xlsx", "Sheet1")]
+ [InlineData("SH114-Centered-Cell.xlsx", "Sheet1")]
+ [InlineData("SH115-DigitsToRight.xlsx", "Sheet1")]
+ [InlineData("SH116-FmtNumId-1.xlsx", "Sheet1")]
+ [InlineData("SH117-FmtNumId-2.xlsx", "Sheet1")]
+ [InlineData("SH118-FmtNumId-3.xlsx", "Sheet1")]
+ [InlineData("SH119-FmtNumId-4.xlsx", "Sheet1")]
+ [InlineData("SH120-FmtNumId-9.xlsx", "Sheet1")]
+ [InlineData("SH121-FmtNumId-11.xlsx", "Sheet1")]
+ [InlineData("SH122-FmtNumId-12.xlsx", "Sheet1")]
+ [InlineData("SH123-FmtNumId-14.xlsx", "Sheet1")]
+ [InlineData("SH124-FmtNumId-15.xlsx", "Sheet1")]
+ [InlineData("SH125-FmtNumId-16.xlsx", "Sheet1")]
+ [InlineData("SH126-FmtNumId-17.xlsx", "Sheet1")]
+ [InlineData("SH127-FmtNumId-18.xlsx", "Sheet1")]
+ [InlineData("SH128-FmtNumId-19.xlsx", "Sheet1")]
+ [InlineData("SH129-FmtNumId-20.xlsx", "Sheet1")]
+ [InlineData("SH130-FmtNumId-21.xlsx", "Sheet1")]
+ [InlineData("SH131-FmtNumId-22.xlsx", "Sheet1")]
+
+ public void SH005_ConvertSheet(string name, string sheetName)
+ {
+ FileInfo sourceXlsx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestXlsx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", "-1-Source.xlsx")));
+ if (!sourceCopiedToDestXlsx.Exists)
+ File.Copy(sourceXlsx.FullName, sourceCopiedToDestXlsx.FullName);
+
+ var dataTemplateFileNameSuffix = "-2-Generated-XmlData-Entire-Sheet.xml";
+ var dataXmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", dataTemplateFileNameSuffix)));
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(sourceXlsx.FullName, true))
+ {
+ var settings = new SmlToHtmlConverterSettings();
+ var rangeXml = SmlDataRetriever.RetrieveSheet(sDoc, sheetName);
+ rangeXml.Save(dataXmlFi.FullName);
+ }
+ }
+
+ [Theory]
+ [InlineData("SH101-SimpleFormats.xlsx", "Sheet1", "A1:B10")]
+ [InlineData("SH101-SimpleFormats.xlsx", "Sheet1", "A4:B8")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "C2:C2")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "A9:A9")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "I1:I1")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "I9:I9")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "A1:I9")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "A2:D4")]
+ [InlineData("SH102-9-x-9.xlsx", "Sheet1", "C5:G7")]
+ [InlineData("SH103-No-SharedString.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH104-With-SharedString.xlsx", "Sheet1", "A4:A7")]
+ [InlineData("SH105-No-SharedString.xlsx", "Sheet1", "A4:A7")]
+ [InlineData("SH106-9-x-9-Formatted.xlsx", "Sheet1", "A1:I9")]
+ [InlineData("SH108-SimpleFormattedCell.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH109-CellWithBorder.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH110-CellWithMasterStyle.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH111-ChangedDefaultColumnWidth.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH112-NotVertMergedCell.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH113-VertMergedCell.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH114-Centered-Cell.xlsx", "Sheet1", "A1:A1")]
+ [InlineData("SH115-DigitsToRight.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH116-FmtNumId-1.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH117-FmtNumId-2.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH118-FmtNumId-3.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH119-FmtNumId-4.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH120-FmtNumId-9.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH121-FmtNumId-11.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH122-FmtNumId-12.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH123-FmtNumId-14.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH124-FmtNumId-15.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH125-FmtNumId-16.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH126-FmtNumId-17.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH127-FmtNumId-18.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH128-FmtNumId-19.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH129-FmtNumId-20.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH130-FmtNumId-21.xlsx", "Sheet1", "A1:A10")]
+ [InlineData("SH131-FmtNumId-22.xlsx", "Sheet1", "A1:A10")]
+
+ public void SH004_ConvertRange(string name, string sheetName, string range)
+ {
+ FileInfo sourceXlsx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestXlsx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", "-1-Source.xlsx")));
+ if (!sourceCopiedToDestXlsx.Exists)
+ File.Copy(sourceXlsx.FullName, sourceCopiedToDestXlsx.FullName);
+
+ var dataTemplateFileNameSuffix = string.Format("-2-Generated-XmlData-{0}.xml", range.Replace(":", ""));
+ var dataXmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", dataTemplateFileNameSuffix)));
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(sourceXlsx.FullName, true))
+ {
+ var settings = new SmlToHtmlConverterSettings();
+ var rangeXml = SmlDataRetriever.RetrieveRange(sDoc, sheetName, range);
+ rangeXml.Save(dataXmlFi.FullName);
+ }
+ }
+
+
+ [Theory]
+ [InlineData("SH001-Table.xlsx", "MyTable")]
+ [InlineData("SH003-TableWithDateInFirstColumn.xlsx", "MyTable")]
+ [InlineData("SH004-TableAtOffsetLocation.xlsx", "MyTable")]
+ [InlineData("SH005-Table-With-SharedStrings.xlsx", "Table1")]
+ [InlineData("SH006-Table-No-SharedStrings.xlsx", "Table1")]
+ [InlineData("SH107-9-x-9-Formatted-Table.xlsx", "Table1")]
+ [InlineData("SH007-One-Cell-Table.xlsx", "Table1")]
+ [InlineData("SH008-Table-With-Tall-Row.xlsx", "Table1")]
+ [InlineData("SH009-Table-With-Wide-Column.xlsx", "Table1")]
+
+ public void SH003_ConvertTable(string name, string tableName)
+ {
+ FileInfo sourceXlsx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestXlsx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", "-1-Source.xlsx")));
+ if (!sourceCopiedToDestXlsx.Exists)
+ File.Copy(sourceXlsx.FullName, sourceCopiedToDestXlsx.FullName);
+
+ var dataXmlFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceXlsx.Name.Replace(".xlsx", "-2-Generated-XmlData.xml")));
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(sourceXlsx.FullName, true))
+ {
+ var settings = new SmlToHtmlConverterSettings();
+ var rangeXml = SmlDataRetriever.RetrieveTable(sDoc, tableName);
+ rangeXml.Save(dataXmlFi.FullName);
+ }
+ }
+
+ [Theory]
+ [InlineData("Spreadsheet.xlsx", 2)]
+ public void SH002_SheetNames(string name, int numberOfSheets)
+ {
+ FileInfo sourceXlsx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(sourceXlsx.FullName, true))
+ {
+ var sheetNames = SmlDataRetriever.SheetNames(sDoc);
+ Assert.Equal(numberOfSheets, sheetNames.Length);
+ }
+ }
+
+ [Theory]
+ [InlineData("SH001-Table.xlsx", 1)]
+ [InlineData("SH002-TwoTablesTwoSheets.xlsx", 2)]
+ public void SH001_TableNames(string name, int numberOfTables)
+ {
+ FileInfo sourceXlsx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(sourceXlsx.FullName, true))
+ {
+ var table = SmlDataRetriever.TableNames(sDoc);
+ Assert.Equal(numberOfTables, table.Length);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/SpreadsheetWriterTests.cs b/OpenXmlPowerTools.Tests/SpreadsheetWriterTests.cs
new file mode 100644
index 0000000..d51180e
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/SpreadsheetWriterTests.cs
@@ -0,0 +1,361 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using Sw = OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class SwTests
+ {
+ [Fact]
+ public void SW001_Simple()
+ {
+ Sw.WorkbookDfn wb = new Sw.WorkbookDfn
+ {
+ Worksheets = new Sw.WorksheetDfn[]
+ {
+ new Sw.WorksheetDfn
+ {
+ Name = "MyFirstSheet",
+ TableName = "NamesAndRates",
+ ColumnHeadings = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn
+ {
+ Value = "Name",
+ Bold = true,
+ },
+ new Sw.CellDfn
+ {
+ Value = "Age",
+ Bold = true,
+ HorizontalCellAlignment = Sw.HorizontalCellAlignment.Left,
+ },
+ new Sw.CellDfn
+ {
+ Value = "Rate",
+ Bold = true,
+ HorizontalCellAlignment = Sw.HorizontalCellAlignment.Left,
+ }
+ },
+ Rows = new Sw.RowDfn[]
+ {
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "Eric",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = 50,
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (decimal)45.00,
+ FormatCode = "0.00",
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "Bob",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = 42,
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (decimal)78.00,
+ FormatCode = "0.00",
+ },
+ }
+ },
+ }
+ }
+ }
+ };
+ var outXlsx = new FileInfo(Path.Combine(Sw.TestUtil.TempDir.FullName, "SW001-Simple.xlsx"));
+ Sw.SpreadsheetWriter.Write(outXlsx.FullName, wb);
+ Validate(outXlsx);
+ }
+
+ [Fact]
+ public void SW002_AllDataTypes()
+ {
+ Sw.WorkbookDfn wb = new Sw.WorkbookDfn
+ {
+ Worksheets = new Sw.WorksheetDfn[]
+ {
+ new Sw.WorksheetDfn
+ {
+ Name = "MyFirstSheet",
+ ColumnHeadings = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn
+ {
+ Value = "DataType",
+ Bold = true,
+ },
+ new Sw.CellDfn
+ {
+ Value = "Value",
+ Bold = true,
+ HorizontalCellAlignment = Sw.HorizontalCellAlignment.Right,
+ },
+ },
+ Rows = new Sw.RowDfn[]
+ {
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "Boolean",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Boolean,
+ Value = true,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "Boolean",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Boolean,
+ Value = false,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "String",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "A String",
+ HorizontalCellAlignment = Sw.HorizontalCellAlignment.Right,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "int",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (int)100,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "int?",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (int?)100,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "int? (is null)",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = null,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "uint",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (uint)101,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "long",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = Int64.MaxValue,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "float",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (float)123.45,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "double",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (double)123.45,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.String,
+ Value = "decimal",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Number,
+ Value = (decimal)123.45,
+ },
+ }
+ },
+ new Sw.RowDfn
+ {
+ Cells = new Sw.CellDfn[]
+ {
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Date,
+ Value = new DateTime(2012, 1, 8),
+ FormatCode = "mm-dd-yy",
+ },
+ new Sw.CellDfn {
+ CellDataType = Sw.CellDataType.Date,
+ Value = new DateTime(2012, 1, 9),
+ FormatCode = "mm-dd-yy",
+ Bold = true,
+ HorizontalCellAlignment = Sw.HorizontalCellAlignment.Center,
+ },
+ }
+ },
+ }
+ }
+ }
+ };
+ var outXlsx = new FileInfo(Path.Combine(Sw.TestUtil.TempDir.FullName, "SW002-DataTypes.xlsx"));
+ Sw.SpreadsheetWriter.Write(outXlsx.FullName, wb);
+ Validate(outXlsx);
+ }
+
+ private void Validate(FileInfo fi)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fi.FullName, true))
+ {
+ OpenXmlValidator v = new OpenXmlValidator();
+ var errors = v.Validate(sDoc).Where(ve => !s_ExpectedErrors.Contains(ve.Description));
+
+#if false
+ // if a test fails validation post-processing, then can use this code to determine the SDK
+ // validation error(s).
+
+ if (errors.Count() != 0)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (var item in errors)
+ {
+ sb.Append(item.Description).Append(Environment.NewLine);
+ }
+ var s = sb.ToString();
+ Console.WriteLine(s);
+ }
+#endif
+
+ Assert.Empty(errors);
+ }
+ }
+
+ private static List<string> s_ExpectedErrors = new List<string>()
+ {
+ "The attribute 't' has invalid value 'd'. The Enumeration constraint failed.",
+ };
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/StronglyTypedBlockTests.cs b/OpenXmlPowerTools.Tests/StronglyTypedBlockTests.cs
new file mode 100644
index 0000000..eaa39de
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/StronglyTypedBlockTests.cs
@@ -0,0 +1,77 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OpenXmlPowerTools.Tests
+{
+ public class StronglyTypedBlockTests : TestsBase
+ {
+ [Fact]
+ public void CanUseStronglyTypedBlockToDemarcateApis()
+ {
+ using (var stream = new MemoryStream())
+ {
+ CreateEmptyWordprocessingDocument(stream);
+
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
+ {
+ MainDocumentPart part = wordDocument.MainDocumentPart;
+
+ // Add a paragraph through the PowerTools.
+ XDocument content = part.GetXDocument();
+ XElement bodyElement = content.Descendants(W.body).First();
+ bodyElement.Add(new XElement(W.p, new XElement(W.r, new XElement(W.t, "Added through PowerTools"))));
+ part.PutXDocument();
+
+ // This demonstrates the use of the StronglyTypedBlock in a using statement to
+ // demarcate the intermittent use of the strongly typed classes.
+ using (new StronglyTypedBlock(wordDocument))
+ {
+ // Assert that we can see the paragraph added through the PowerTools.
+ Body body = part.Document.Body;
+ List<Paragraph> paragraphs = body.Elements<Paragraph>().ToList();
+ Assert.Single(paragraphs);
+ Assert.Equal("Added through PowerTools", paragraphs[0].InnerText);
+
+ // Add a paragraph through the SDK.
+ body.AppendChild(new Paragraph(new Run(new Text("Added through SDK"))));
+ }
+
+ // Assert that we can see the paragraphs added through the PowerTools and the SDK.
+ content = part.GetXDocument();
+ List<XElement> paragraphElements = content.Descendants(W.p).ToList();
+ Assert.Equal(2, paragraphElements.Count);
+ Assert.Equal("Added through PowerTools", paragraphElements[0].Value);
+ Assert.Equal("Added through SDK", paragraphElements[1].Value);
+ }
+ }
+ }
+
+ [Fact]
+ public void ConstructorThrowsWhenPassingNull()
+ {
+ Assert.Throws<ArgumentNullException>(() => new StronglyTypedBlock(null));
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/TestsBase.cs b/OpenXmlPowerTools.Tests/TestsBase.cs
new file mode 100644
index 0000000..56b49b3
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/TestsBase.cs
@@ -0,0 +1,36 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System.IO;
+using DocumentFormat.OpenXml;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+
+namespace OpenXmlPowerTools.Tests
+{
+ /// <summary>
+ /// Base class for unit tests providing utility methods.
+ /// </summary>
+ public class TestsBase
+ {
+ private const WordprocessingDocumentType DocumentType = WordprocessingDocumentType.Document;
+
+ protected static void CreateEmptyWordprocessingDocument(Stream stream)
+ {
+ using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, DocumentType))
+ {
+ MainDocumentPart part = wordDocument.AddMainDocumentPart();
+ part.Document = new Document(new Body());
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools.Tests/UnicodeMapperTests.cs b/OpenXmlPowerTools.Tests/UnicodeMapperTests.cs
new file mode 100644
index 0000000..feb3ba4
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/UnicodeMapperTests.cs
@@ -0,0 +1,145 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2016.
+
+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
+
+Developer: Thomas Barnekow
+Email: thomas@barnekow.info
+
+***************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OpenXmlPowerTools.Tests
+{
+ public class UnicodeMapperTests
+ {
+ [Fact]
+ public void CanStringifyRunAndTextElements()
+ {
+ const string textValue = "Hello World!";
+ var textElement = new XElement(W.t, textValue);
+ var runElement = new XElement(W.r, textElement);
+ var formattedRunElement = new XElement(W.r, new XElement(W.rPr, new XElement(W.b)), textElement);
+
+ Assert.Equal(textValue, UnicodeMapper.RunToString(textElement));
+ Assert.Equal(textValue, UnicodeMapper.RunToString(runElement));
+ Assert.Equal(textValue, UnicodeMapper.RunToString(formattedRunElement));
+ }
+
+ [Fact]
+ public void CanStringifySpecialElements()
+ {
+ Assert.Equal(UnicodeMapper.CarriageReturn,
+ UnicodeMapper.RunToString(new XElement(W.cr)).First());
+ Assert.Equal(UnicodeMapper.CarriageReturn,
+ UnicodeMapper.RunToString(new XElement(W.br)).First());
+ Assert.Equal(UnicodeMapper.FormFeed,
+ UnicodeMapper.RunToString(new XElement(W.br, new XAttribute(W.type, "page"))).First());
+ Assert.Equal(UnicodeMapper.NonBreakingHyphen,
+ UnicodeMapper.RunToString(new XElement(W.noBreakHyphen)).First());
+ Assert.Equal(UnicodeMapper.SoftHyphen,
+ UnicodeMapper.RunToString(new XElement(W.softHyphen)).First());
+ Assert.Equal(UnicodeMapper.HorizontalTabulation,
+ UnicodeMapper.RunToString(new XElement(W.tab)).First());
+ }
+
+ [Fact]
+ public void CanCreateRunChildElementsFromSpecialCharacters()
+ {
+ Assert.Equal(W.br, UnicodeMapper.CharToRunChild(UnicodeMapper.CarriageReturn).Name);
+ Assert.Equal(W.noBreakHyphen, UnicodeMapper.CharToRunChild(UnicodeMapper.NonBreakingHyphen).Name);
+ Assert.Equal(W.softHyphen, UnicodeMapper.CharToRunChild(UnicodeMapper.SoftHyphen).Name);
+ Assert.Equal(W.tab, UnicodeMapper.CharToRunChild(UnicodeMapper.HorizontalTabulation).Name);
+
+ XElement element = UnicodeMapper.CharToRunChild(UnicodeMapper.FormFeed);
+ Assert.Equal(W.br, element.Name);
+ Assert.Equal("page", element.Attribute(W.type).Value);
+
+ Assert.Equal(W.br, UnicodeMapper.CharToRunChild('\r').Name);
+ }
+
+ [Fact]
+ public void CanCreateCoalescedRuns()
+ {
+ const string textString = "This is only text.";
+ const string mixedString = "First\tSecond\tThird";
+
+ List<XElement> textRuns = UnicodeMapper.StringToCoalescedRunList(textString, null);
+ List<XElement> mixedRuns = UnicodeMapper.StringToCoalescedRunList(mixedString, null);
+
+ Assert.Single(textRuns);
+ Assert.Equal(5, mixedRuns.Count);
+
+ Assert.Equal("First", mixedRuns.Elements(W.t).Skip(0).First().Value);
+ Assert.Equal("Second", mixedRuns.Elements(W.t).Skip(1).First().Value);
+ Assert.Equal("Third", mixedRuns.Elements(W.t).Skip(2).First().Value);
+ }
+
+ [Fact]
+ public void CanMapSymbols()
+ {
+ var sym1 = new XElement(W.sym,
+ new XAttribute(W.font, "Wingdings"),
+ new XAttribute(W._char, "F028"));
+ char charFromSym1 = UnicodeMapper.SymToChar(sym1);
+ XElement symFromChar1 = UnicodeMapper.CharToRunChild(charFromSym1);
+
+ var sym2 = new XElement(W.sym,
+ new XAttribute(W._char, "F028"),
+ new XAttribute(W.font, "Wingdings"));
+ char charFromSym2 = UnicodeMapper.SymToChar(sym2);
+
+ var sym3 = new XElement(W.sym,
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(W.font, "Wingdings"),
+ new XAttribute(W._char, "F028"));
+ char charFromSym3 = UnicodeMapper.SymToChar(sym3);
+
+ var sym4 = new XElement(W.sym,
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(W.font, "Webdings"),
+ new XAttribute(W._char, "F028"));
+ char charFromSym4 = UnicodeMapper.SymToChar(sym4);
+ XElement symFromChar4 = UnicodeMapper.CharToRunChild(charFromSym4);
+
+ Assert.Equal(charFromSym1, charFromSym2);
+ Assert.Equal(charFromSym1, charFromSym3);
+ Assert.NotEqual(charFromSym1, charFromSym4);
+
+ Assert.Equal("F028", symFromChar1.Attribute(W._char).Value);
+ Assert.Equal("Wingdings", symFromChar1.Attribute(W.font).Value);
+
+ Assert.Equal("F028", symFromChar4.Attribute(W._char).Value);
+ Assert.Equal("Webdings", symFromChar4.Attribute(W.font).Value);
+ }
+
+ [Fact]
+ public void CanStringifySymbols()
+ {
+ char charFromSym1 = UnicodeMapper.SymToChar("Wingdings", '\uF028');
+ char charFromSym2 = UnicodeMapper.SymToChar("Wingdings", 0xF028);
+ char charFromSym3 = UnicodeMapper.SymToChar("Wingdings", "F028");
+
+ XElement symFromChar1 = UnicodeMapper.CharToRunChild(charFromSym1);
+ XElement symFromChar2 = UnicodeMapper.CharToRunChild(charFromSym2);
+ XElement symFromChar3 = UnicodeMapper.CharToRunChild(charFromSym3);
+
+ Assert.Equal(charFromSym1, charFromSym2);
+ Assert.Equal(charFromSym1, charFromSym3);
+
+ Assert.Equal(symFromChar1.ToString(SaveOptions.None), symFromChar2.ToString(SaveOptions.None));
+ Assert.Equal(symFromChar1.ToString(SaveOptions.None), symFromChar3.ToString(SaveOptions.None));
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/WmlComparerTests.cs b/OpenXmlPowerTools.Tests/WmlComparerTests.cs
new file mode 100644
index 0000000..52ca8a4
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/WmlComparerTests.cs
@@ -0,0 +1,1124 @@
+/***************************************************************************
+
+Copyright (c) Eric White 2016.
+
+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
+
+Published at http://EricWhite.com
+Resource Center and Documentation: http://ericwhite.com/blog/blog/open-xml-powertools-developer-center/
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+using System.Diagnostics;
+
+/****************************************************************************************************************/
+// Large tests have been commented out below. If and when there is an effort to improve performance for WmlComparer,
+// then uncomment. Performance isn't bad, but certainly is possible to improve.
+/****************************************************************************************************************/
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class WcTests
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ public static bool s_OpenWord = false;
+ public static bool m_OpenTempDirInExplorer = false;
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ [Theory]
+ [InlineData("RC-0010", "RC/RC001-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC001-After1.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ <RcInfo>
+ <DocName>RC/RC001-After2.docx</DocName>
+ <Color>LightPink</Color>
+ <Revisor>From Fred</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0020", "RC/RC002-Image.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC002-Image-After1.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0030", "RC/RC002-Image-After1.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC002-Image.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0040", "WC/WC027-Twenty-Paras-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>WC/WC027-Twenty-Paras-After-1.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0050", "WC/WC027-Twenty-Paras-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>WC/WC027-Twenty-Paras-After-3.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0060", "RC/RC003-Multi-Paras.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC003-Multi-Paras-After.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0070", "RC/RC004-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC004-After1.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ <RcInfo>
+ <DocName>RC/RC004-After2.docx</DocName>
+ <Color>LightPink</Color>
+ <Revisor>From Fred</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0080", "RC/RC005-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC005-After1.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0090", "RC/RC006-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC006-After1.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC-0100", "RC/RC007-Endnotes-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC/RC007-Endnotes-After.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+
+ public void WC001_Consolidate(string testId, string originalName, string revisedDocumentsXml)
+ {
+ FileInfo originalDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, originalName));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id: " + testId);
+ else
+ thisTestTempDir.Create();
+
+ var originalCopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, originalDocx.Name));
+ if (!originalCopiedToDestDocx.Exists)
+ {
+ var wml1 = new WmlDocument(originalDocx.FullName);
+ var wml2 = WordprocessingMLUtil.BreakLinkToTemplate(wml1);
+ wml2.SaveAs(originalCopiedToDestDocx.FullName);
+ }
+
+ var revisedDocumentsXElement = XElement.Parse(revisedDocumentsXml);
+ var revisedDocumentsArray = revisedDocumentsXElement
+ .Elements()
+ .Select(z =>
+ {
+ FileInfo revisedDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, z.Element("DocName").Value));
+ var revisedCopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, revisedDocx.Name));
+ var wml1 = new WmlDocument(revisedDocx.FullName);
+ var wml2 = WordprocessingMLUtil.BreakLinkToTemplate(wml1);
+ wml2.SaveAs(revisedCopiedToDestDocx.FullName);
+ return new WmlRevisedDocumentInfo()
+ {
+ RevisedDocument = new WmlDocument(revisedCopiedToDestDocx.FullName),
+ Color = Color.FromName(z.Element("Color").Value),
+ Revisor = z.Element("Revisor").Value,
+ };
+ })
+ .ToList();
+
+ var consolidatedDocxName = originalCopiedToDestDocx.Name.Replace(".docx", "-Consolidated.docx");
+ var consolidatedDocumentFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, consolidatedDocxName));
+
+ WmlDocument source1Wml = new WmlDocument(originalCopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ settings.DebugTempFileDi = thisTestTempDir;
+ WmlDocument consolidatedWml = WmlComparer.Consolidate(
+ source1Wml,
+ revisedDocumentsArray,
+ settings);
+ var wml3 = WordprocessingMLUtil.BreakLinkToTemplate(consolidatedWml);
+ wml3.SaveAs(consolidatedDocumentFi.FullName);
+
+ var validationErrors = "";
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(consolidatedWml.DocumentByteArray, 0, consolidatedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+ }
+ validationErrors = sb.ToString();
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, consolidatedDocumentFi);
+ WordRunner.RunWord(wordExe, originalCopiedToDestDocx);
+
+ var revisedList = revisedDocumentsXElement
+ .Elements()
+ .Select(z =>
+ {
+ FileInfo revisedDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, z.Element("DocName").Value));
+ var revisedCopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, revisedDocx.Name));
+ return revisedCopiedToDestDocx;
+ })
+ .ToList();
+ foreach (var item in revisedList)
+ WordRunner.RunWord(wordExe, item);
+ }
+
+ /************************************************************************************************************************/
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Open Windows Explorer
+ if (m_OpenTempDirInExplorer)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var semaphorFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "z_ExplorerOpenedSemaphore.txt"));
+ if (!semaphorFi.Exists)
+ {
+ File.WriteAllText(semaphorFi.FullName, "");
+ TestUtil.Explorer(thisTestTempDir);
+ }
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ if (validationErrors != "")
+ Assert.True(false, validationErrors);
+ }
+
+ [Theory]
+ [InlineData("WCB-1000", "CA/CA001-Plain.docx", "CA/CA001-Plain-Mod.docx")]
+ [InlineData("WCB-1010", "WC/WC001-Digits.docx", "WC/WC001-Digits-Mod.docx")]
+ [InlineData("WCB-1020", "WC/WC001-Digits.docx", "WC/WC001-Digits-Deleted-Paragraph.docx")]
+ [InlineData("WCB-1030", "WC/WC001-Digits-Deleted-Paragraph.docx", "WC/WC001-Digits.docx")]
+ [InlineData("WCB-1040", "WC/WC002-Unmodified.docx", "WC/WC002-DiffInMiddle.docx")]
+ [InlineData("WCB-1050", "WC/WC002-Unmodified.docx", "WC/WC002-DiffAtBeginning.docx")]
+ [InlineData("WCB-1060", "WC/WC002-Unmodified.docx", "WC/WC002-DeleteAtBeginning.docx")]
+ [InlineData("WCB-1070", "WC/WC002-Unmodified.docx", "WC/WC002-InsertAtBeginning.docx")]
+ [InlineData("WCB-1080", "WC/WC002-Unmodified.docx", "WC/WC002-InsertAtEnd.docx")]
+ [InlineData("WCB-1090", "WC/WC002-Unmodified.docx", "WC/WC002-DeleteAtEnd.docx")]
+ [InlineData("WCB-1100", "WC/WC002-Unmodified.docx", "WC/WC002-DeleteInMiddle.docx")]
+ [InlineData("WCB-1110", "WC/WC002-Unmodified.docx", "WC/WC002-InsertInMiddle.docx")]
+ [InlineData("WCB-1120", "WC/WC002-DeleteInMiddle.docx", "WC/WC002-Unmodified.docx")]
+ //[InlineData("WCB-1130", "WC/WC004-Large.docx", "WC/WC004-Large-Mod.docx")]
+ [InlineData("WCB-1140", "WC/WC006-Table.docx", "WC/WC006-Table-Delete-Row.docx")]
+ [InlineData("WCB-1150", "WC/WC006-Table-Delete-Row.docx", "WC/WC006-Table.docx")]
+ [InlineData("WCB-1160", "WC/WC006-Table.docx", "WC/WC006-Table-Delete-Contests-of-Row.docx")]
+ [InlineData("WCB-1170", "WC/WC007-Unmodified.docx", "WC/WC007-Longest-At-End.docx")]
+ [InlineData("WCB-1180", "WC/WC007-Unmodified.docx", "WC/WC007-Deleted-at-Beginning-of-Para.docx")]
+ [InlineData("WCB-1190", "WC/WC007-Unmodified.docx", "WC/WC007-Moved-into-Table.docx")]
+ [InlineData("WCB-1200", "WC/WC009-Table-Unmodified.docx", "WC/WC009-Table-Cell-1-1-Mod.docx")]
+ [InlineData("WCB-1210", "WC/WC010-Para-Before-Table-Unmodified.docx", "WC/WC010-Para-Before-Table-Mod.docx")]
+ [InlineData("WCB-1220", "WC/WC011-Before.docx", "WC/WC011-After.docx")]
+ [InlineData("WCB-1230", "WC/WC012-Math-Before.docx", "WC/WC012-Math-After.docx")]
+ [InlineData("WCB-1240", "WC/WC013-Image-Before.docx", "WC/WC013-Image-After.docx")]
+ [InlineData("WCB-1250", "WC/WC013-Image-Before.docx", "WC/WC013-Image-After2.docx")]
+ [InlineData("WCB-1260", "WC/WC013-Image-Before2.docx", "WC/WC013-Image-After2.docx")]
+ [InlineData("WCB-1270", "WC/WC014-SmartArt-Before.docx", "WC/WC014-SmartArt-After.docx")]
+ [InlineData("WCB-1280", "WC/WC014-SmartArt-With-Image-Before.docx", "WC/WC014-SmartArt-With-Image-After.docx")]
+ [InlineData("WCB-1290", "WC/WC014-SmartArt-With-Image-Before.docx", "WC/WC014-SmartArt-With-Image-Deleted-After.docx")]
+ [InlineData("WCB-1300", "WC/WC014-SmartArt-With-Image-Before.docx", "WC/WC014-SmartArt-With-Image-Deleted-After2.docx")]
+ [InlineData("WCB-1310", "WC/WC015-Three-Paragraphs.docx", "WC/WC015-Three-Paragraphs-After.docx")]
+ [InlineData("WCB-1320", "WC/WC016-Para-Image-Para.docx", "WC/WC016-Para-Image-Para-w-Deleted-Image.docx")]
+ [InlineData("WCB-1330", "WC/WC017-Image.docx", "WC/WC017-Image-After.docx")]
+ [InlineData("WCB-1340", "WC/WC018-Field-Simple-Before.docx", "WC/WC018-Field-Simple-After-1.docx")]
+ [InlineData("WCB-1350", "WC/WC018-Field-Simple-Before.docx", "WC/WC018-Field-Simple-After-2.docx")]
+ [InlineData("WCB-1360", "WC/WC019-Hyperlink-Before.docx", "WC/WC019-Hyperlink-After-1.docx")]
+ [InlineData("WCB-1370", "WC/WC019-Hyperlink-Before.docx", "WC/WC019-Hyperlink-After-2.docx")]
+ [InlineData("WCB-1380", "WC/WC020-FootNote-Before.docx", "WC/WC020-FootNote-After-1.docx")]
+ [InlineData("WCB-1390", "WC/WC020-FootNote-Before.docx", "WC/WC020-FootNote-After-2.docx")]
+ [InlineData("WCB-1400", "WC/WC021-Math-Before-1.docx", "WC/WC021-Math-After-1.docx")]
+ [InlineData("WCB-1410", "WC/WC021-Math-Before-2.docx", "WC/WC021-Math-After-2.docx")]
+ [InlineData("WCB-1420", "WC/WC022-Image-Math-Para-Before.docx", "WC/WC022-Image-Math-Para-After.docx")]
+ [InlineData("WCB-1430", "WC/WC023-Table-4-Row-Image-Before.docx", "WC/WC023-Table-4-Row-Image-After-Delete-1-Row.docx")]
+ [InlineData("WCB-1440", "WC/WC024-Table-Before.docx", "WC/WC024-Table-After.docx")]
+ [InlineData("WCB-1450", "WC/WC024-Table-Before.docx", "WC/WC024-Table-After2.docx")]
+ [InlineData("WCB-1460", "WC/WC025-Simple-Table-Before.docx", "WC/WC025-Simple-Table-After.docx")]
+ [InlineData("WCB-1470", "WC/WC026-Long-Table-Before.docx", "WC/WC026-Long-Table-After-1.docx")]
+ [InlineData("WCB-1480", "WC/WC027-Twenty-Paras-Before.docx", "WC/WC027-Twenty-Paras-After-1.docx")]
+ [InlineData("WCB-1490", "WC/WC027-Twenty-Paras-After-1.docx", "WC/WC027-Twenty-Paras-Before.docx")]
+ [InlineData("WCB-1500", "WC/WC027-Twenty-Paras-Before.docx", "WC/WC027-Twenty-Paras-After-2.docx")]
+ [InlineData("WCB-1510", "WC/WC030-Image-Math-Before.docx", "WC/WC030-Image-Math-After.docx")]
+ [InlineData("WCB-1520", "WC/WC031-Two-Maths-Before.docx", "WC/WC031-Two-Maths-After.docx")]
+ [InlineData("WCB-1530", "WC/WC032-Para-with-Para-Props.docx", "WC/WC032-Para-with-Para-Props-After.docx")]
+ [InlineData("WCB-1540", "WC/WC033-Merged-Cells-Before.docx", "WC/WC033-Merged-Cells-After1.docx")]
+ [InlineData("WCB-1550", "WC/WC033-Merged-Cells-Before.docx", "WC/WC033-Merged-Cells-After2.docx")]
+ [InlineData("WCB-1560", "WC/WC034-Footnotes-Before.docx", "WC/WC034-Footnotes-After1.docx")]
+ [InlineData("WCB-1570", "WC/WC034-Footnotes-Before.docx", "WC/WC034-Footnotes-After2.docx")]
+ [InlineData("WCB-1580", "WC/WC034-Footnotes-Before.docx", "WC/WC034-Footnotes-After3.docx")]
+ [InlineData("WCB-1590", "WC/WC034-Footnotes-After3.docx", "WC/WC034-Footnotes-Before.docx")]
+ [InlineData("WCB-1600", "WC/WC035-Footnote-Before.docx", "WC/WC035-Footnote-After.docx")]
+ [InlineData("WCB-1610", "WC/WC035-Footnote-After.docx", "WC/WC035-Footnote-Before.docx")]
+ [InlineData("WCB-1620", "WC/WC036-Footnote-With-Table-Before.docx", "WC/WC036-Footnote-With-Table-After.docx")]
+ [InlineData("WCB-1630", "WC/WC036-Footnote-With-Table-After.docx", "WC/WC036-Footnote-With-Table-Before.docx")]
+ [InlineData("WCB-1640", "WC/WC034-Endnotes-Before.docx", "WC/WC034-Endnotes-After1.docx")]
+ [InlineData("WCB-1650", "WC/WC034-Endnotes-Before.docx", "WC/WC034-Endnotes-After2.docx")]
+ [InlineData("WCB-1660", "WC/WC034-Endnotes-Before.docx", "WC/WC034-Endnotes-After3.docx")]
+ [InlineData("WCB-1670", "WC/WC034-Endnotes-After3.docx", "WC/WC034-Endnotes-Before.docx")]
+ [InlineData("WCB-1680", "WC/WC035-Endnote-Before.docx", "WC/WC035-Endnote-After.docx")]
+ [InlineData("WCB-1690", "WC/WC035-Endnote-After.docx", "WC/WC035-Endnote-Before.docx")]
+ [InlineData("WCB-1700", "WC/WC036-Endnote-With-Table-Before.docx", "WC/WC036-Endnote-With-Table-After.docx")]
+ [InlineData("WCB-1710", "WC/WC036-Endnote-With-Table-After.docx", "WC/WC036-Endnote-With-Table-Before.docx")]
+ [InlineData("WCB-1720", "WC/WC038-Document-With-BR-Before.docx", "WC/WC038-Document-With-BR-After.docx")]
+ [InlineData("WCB-1730", "RC/RC001-Before.docx", "RC/RC001-After1.docx")]
+ [InlineData("WCB-1740", "RC/RC002-Image.docx", "RC/RC002-Image-After1.docx")]
+ //[InlineData("WCB-1000", "", "")]
+ //[InlineData("WCB-1000", "", "")]
+ //[InlineData("WCB-1000", "", "")]
+ //[InlineData("WCB-1000", "", "")]
+ //[InlineData("WCB-1000", "", "")]
+ //[InlineData("WCB-1000", "", "")]
+ //[InlineData("WCB-1000", "", "")]
+
+
+ public void WC002_Consolidate_Bulk_Test(string testId, string name1, string name2)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id: " + testId);
+ else
+ thisTestTempDir.Create();
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ {
+ var wml1 = new WmlDocument(source1Docx.FullName);
+ var wml2 = WordprocessingMLUtil.BreakLinkToTemplate(wml1);
+ wml2.SaveAs(source1CopiedToDestDocx.FullName);
+ }
+ if (!source2CopiedToDestDocx.Exists)
+ {
+ var wml1 = new WmlDocument(source2Docx.FullName);
+ var wml2 = WordprocessingMLUtil.BreakLinkToTemplate(wml1);
+ wml2.SaveAs(source2CopiedToDestDocx.FullName);
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ if (!source1CopiedToDestDocxForWord.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ if (!source2CopiedToDestDocxForWord.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ var path = new DirectoryInfo(@"C:\Users\Eric\Documents\WindowsPowerShellModules\Open-Xml-PowerTools\TestFiles");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ /************************************************************************************************************************/
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+ var docxConsolidatedFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-CONSOLIDATED-" + after + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ WordprocessingMLUtil.BreakLinkToTemplate(comparedWml).SaveAs(docxWithRevisionsFi.FullName);
+
+ List<WmlRevisedDocumentInfo> revisedDocInfo = new List<WmlRevisedDocumentInfo>()
+ {
+ new WmlRevisedDocumentInfo()
+ {
+ RevisedDocument = source2Wml,
+ Color = Color.LightBlue,
+ Revisor = "Revised by Eric White",
+ }
+ };
+ WmlDocument consolidatedWml = WmlComparer.Consolidate(
+ source1Wml,
+ revisedDocInfo,
+ settings);
+ WordprocessingMLUtil.BreakLinkToTemplate(consolidatedWml).SaveAs(docxConsolidatedFi.FullName);
+
+ string validationErrors = "";
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(consolidatedWml.DocumentByteArray, 0, consolidatedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+ }
+ validationErrors = sb.ToString();
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxConsolidatedFi);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Open Windows Explorer
+ if (m_OpenTempDirInExplorer)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var semaphorFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "z_ExplorerOpenedSemaphore.txt"));
+ if (!semaphorFi.Exists)
+ {
+ File.WriteAllText(semaphorFi.FullName, "");
+ TestUtil.Explorer(thisTestTempDir);
+ }
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ if (validationErrors != "")
+ Assert.True(false, validationErrors);
+ }
+
+ [Theory]
+ [InlineData("WC-1000", "CA/CA001-Plain.docx", "CA/CA001-Plain-Mod.docx", 1)]
+ [InlineData("WC-1010", "WC/WC001-Digits.docx", "WC/WC001-Digits-Mod.docx", 4)]
+ [InlineData("WC-1020", "WC/WC001-Digits.docx", "WC/WC001-Digits-Deleted-Paragraph.docx", 1)]
+ [InlineData("WC-1030", "WC/WC001-Digits-Deleted-Paragraph.docx", "WC/WC001-Digits.docx", 1)]
+ [InlineData("WC-1040", "WC/WC002-Unmodified.docx", "WC/WC002-DiffInMiddle.docx", 2)]
+ [InlineData("WC-1050", "WC/WC002-Unmodified.docx", "WC/WC002-DiffAtBeginning.docx", 2)]
+ [InlineData("WC-1060", "WC/WC002-Unmodified.docx", "WC/WC002-DeleteAtBeginning.docx", 1)]
+ [InlineData("WC-1070", "WC/WC002-Unmodified.docx", "WC/WC002-InsertAtBeginning.docx", 1)]
+ [InlineData("WC-1080", "WC/WC002-Unmodified.docx", "WC/WC002-InsertAtEnd.docx", 1)]
+ [InlineData("WC-1090", "WC/WC002-Unmodified.docx", "WC/WC002-DeleteAtEnd.docx", 1)]
+ [InlineData("WC-1100", "WC/WC002-Unmodified.docx", "WC/WC002-DeleteInMiddle.docx", 1)]
+ [InlineData("WC-1110", "WC/WC002-Unmodified.docx", "WC/WC002-InsertInMiddle.docx", 1)]
+ [InlineData("WC-1120", "WC/WC002-DeleteInMiddle.docx", "WC/WC002-Unmodified.docx", 1)]
+ //[InlineData("WC-1130", "WC/WC004-Large.docx", "WC/WC004-Large-Mod.docx", 2)]
+ [InlineData("WC-1140", "WC/WC006-Table.docx", "WC/WC006-Table-Delete-Row.docx", 1)]
+ [InlineData("WC-1150", "WC/WC006-Table-Delete-Row.docx", "WC/WC006-Table.docx", 1)]
+ [InlineData("WC-1160", "WC/WC006-Table.docx", "WC/WC006-Table-Delete-Contests-of-Row.docx", 2)]
+ [InlineData("WC-1170", "WC/WC007-Unmodified.docx", "WC/WC007-Longest-At-End.docx", 2)]
+ [InlineData("WC-1180", "WC/WC007-Unmodified.docx", "WC/WC007-Deleted-at-Beginning-of-Para.docx", 1)]
+ [InlineData("WC-1190", "WC/WC007-Unmodified.docx", "WC/WC007-Moved-into-Table.docx", 2)]
+ [InlineData("WC-1200", "WC/WC009-Table-Unmodified.docx", "WC/WC009-Table-Cell-1-1-Mod.docx", 1)]
+ [InlineData("WC-1210", "WC/WC010-Para-Before-Table-Unmodified.docx", "WC/WC010-Para-Before-Table-Mod.docx", 3)]
+ [InlineData("WC-1220", "WC/WC011-Before.docx", "WC/WC011-After.docx", 2)]
+ [InlineData("WC-1230", "WC/WC012-Math-Before.docx", "WC/WC012-Math-After.docx", 2)]
+ [InlineData("WC-1240", "WC/WC013-Image-Before.docx", "WC/WC013-Image-After.docx", 2)]
+ [InlineData("WC-1250", "WC/WC013-Image-Before.docx", "WC/WC013-Image-After2.docx", 2)]
+ [InlineData("WC-1260", "WC/WC013-Image-Before2.docx", "WC/WC013-Image-After2.docx", 2)]
+ [InlineData("WC-1270", "WC/WC014-SmartArt-Before.docx", "WC/WC014-SmartArt-After.docx", 2)]
+ [InlineData("WC-1280", "WC/WC014-SmartArt-With-Image-Before.docx", "WC/WC014-SmartArt-With-Image-After.docx", 2)]
+ [InlineData("WC-1310", "WC/WC014-SmartArt-With-Image-Before.docx", "WC/WC014-SmartArt-With-Image-Deleted-After.docx", 3)]
+ [InlineData("WC-1320", "WC/WC014-SmartArt-With-Image-Before.docx", "WC/WC014-SmartArt-With-Image-Deleted-After2.docx", 1)]
+ [InlineData("WC-1330", "WC/WC015-Three-Paragraphs.docx", "WC/WC015-Three-Paragraphs-After.docx", 3)]
+ [InlineData("WC-1340", "WC/WC016-Para-Image-Para.docx", "WC/WC016-Para-Image-Para-w-Deleted-Image.docx", 1)]
+ [InlineData("WC-1350", "WC/WC017-Image.docx", "WC/WC017-Image-After.docx", 3)]
+ [InlineData("WC-1360", "WC/WC018-Field-Simple-Before.docx", "WC/WC018-Field-Simple-After-1.docx", 2)]
+ [InlineData("WC-1370", "WC/WC018-Field-Simple-Before.docx", "WC/WC018-Field-Simple-After-2.docx", 3)]
+ [InlineData("WC-1380", "WC/WC019-Hyperlink-Before.docx", "WC/WC019-Hyperlink-After-1.docx", 3)]
+ [InlineData("WC-1390", "WC/WC019-Hyperlink-Before.docx", "WC/WC019-Hyperlink-After-2.docx", 5)]
+ [InlineData("WC-1400", "WC/WC020-FootNote-Before.docx", "WC/WC020-FootNote-After-1.docx", 3)]
+ [InlineData("WC-1410", "WC/WC020-FootNote-Before.docx", "WC/WC020-FootNote-After-2.docx", 5)]
+ [InlineData("WC-1420", "WC/WC021-Math-Before-1.docx", "WC/WC021-Math-After-1.docx", 9)]
+ [InlineData("WC-1430", "WC/WC021-Math-Before-2.docx", "WC/WC021-Math-After-2.docx", 6)]
+ [InlineData("WC-1440", "WC/WC022-Image-Math-Para-Before.docx", "WC/WC022-Image-Math-Para-After.docx", 10)]
+ [InlineData("WC-1450", "WC/WC023-Table-4-Row-Image-Before.docx", "WC/WC023-Table-4-Row-Image-After-Delete-1-Row.docx", 7)]
+ [InlineData("WC-1460", "WC/WC024-Table-Before.docx", "WC/WC024-Table-After.docx", 1)]
+ [InlineData("WC-1470", "WC/WC024-Table-Before.docx", "WC/WC024-Table-After2.docx", 7)]
+ [InlineData("WC-1480", "WC/WC025-Simple-Table-Before.docx", "WC/WC025-Simple-Table-After.docx", 4)]
+ [InlineData("WC-1500", "WC/WC026-Long-Table-Before.docx", "WC/WC026-Long-Table-After-1.docx", 2)]
+ [InlineData("WC-1510", "WC/WC027-Twenty-Paras-Before.docx", "WC/WC027-Twenty-Paras-After-1.docx", 2)]
+ [InlineData("WC-1520", "WC/WC027-Twenty-Paras-After-1.docx", "WC/WC027-Twenty-Paras-Before.docx", 2)]
+ [InlineData("WC-1530", "WC/WC027-Twenty-Paras-Before.docx", "WC/WC027-Twenty-Paras-After-2.docx", 4)]
+ [InlineData("WC-1540", "WC/WC030-Image-Math-Before.docx", "WC/WC030-Image-Math-After.docx", 2)]
+ [InlineData("WC-1550", "WC/WC031-Two-Maths-Before.docx", "WC/WC031-Two-Maths-After.docx", 4)]
+ [InlineData("WC-1560", "WC/WC032-Para-with-Para-Props.docx", "WC/WC032-Para-with-Para-Props-After.docx", 3)]
+ [InlineData("WC-1570", "WC/WC033-Merged-Cells-Before.docx", "WC/WC033-Merged-Cells-After1.docx", 2)]
+ [InlineData("WC-1580", "WC/WC033-Merged-Cells-Before.docx", "WC/WC033-Merged-Cells-After2.docx", 4)]
+ [InlineData("WC-1600", "WC/WC034-Footnotes-Before.docx", "WC/WC034-Footnotes-After1.docx", 1)]
+ [InlineData("WC-1610", "WC/WC034-Footnotes-Before.docx", "WC/WC034-Footnotes-After2.docx", 4)]
+ [InlineData("WC-1620", "WC/WC034-Footnotes-Before.docx", "WC/WC034-Footnotes-After3.docx", 3)]
+ [InlineData("WC-1630", "WC/WC034-Footnotes-After3.docx", "WC/WC034-Footnotes-Before.docx", 3)]
+ [InlineData("WC-1640", "WC/WC035-Footnote-Before.docx", "WC/WC035-Footnote-After.docx", 2)]
+ [InlineData("WC-1650", "WC/WC035-Footnote-After.docx", "WC/WC035-Footnote-Before.docx", 2)]
+ [InlineData("WC-1660", "WC/WC036-Footnote-With-Table-Before.docx", "WC/WC036-Footnote-With-Table-After.docx", 5)]
+ [InlineData("WC-1670", "WC/WC036-Footnote-With-Table-After.docx", "WC/WC036-Footnote-With-Table-Before.docx", 5)]
+ [InlineData("WC-1680", "WC/WC034-Endnotes-Before.docx", "WC/WC034-Endnotes-After1.docx", 1)]
+ [InlineData("WC-1700", "WC/WC034-Endnotes-Before.docx", "WC/WC034-Endnotes-After2.docx", 4)]
+ [InlineData("WC-1710", "WC/WC034-Endnotes-Before.docx", "WC/WC034-Endnotes-After3.docx", 7)]
+ [InlineData("WC-1720", "WC/WC034-Endnotes-After3.docx", "WC/WC034-Endnotes-Before.docx", 7)]
+ [InlineData("WC-1730", "WC/WC035-Endnote-Before.docx", "WC/WC035-Endnote-After.docx", 2)]
+ [InlineData("WC-1740", "WC/WC035-Endnote-After.docx", "WC/WC035-Endnote-Before.docx", 2)]
+ [InlineData("WC-1750", "WC/WC036-Endnote-With-Table-Before.docx", "WC/WC036-Endnote-With-Table-After.docx", 6)]
+ [InlineData("WC-1760", "WC/WC036-Endnote-With-Table-After.docx", "WC/WC036-Endnote-With-Table-Before.docx", 6)]
+ [InlineData("WC-1770", "WC/WC037-Textbox-Before.docx", "WC/WC037-Textbox-After1.docx", 2)]
+ [InlineData("WC-1780", "WC/WC038-Document-With-BR-Before.docx", "WC/WC038-Document-With-BR-After.docx", 2)]
+ [InlineData("WC-1800", "RC/RC001-Before.docx", "RC/RC001-After1.docx", 2)]
+ [InlineData("WC-1810", "RC/RC002-Image.docx", "RC/RC002-Image-After1.docx", 1)]
+ [InlineData("WC-1820", "WC/WC039-Break-In-Row.docx", "WC/WC039-Break-In-Row-After1.docx", 1)]
+ [InlineData("WC-1830", "WC/WC041-Table-5.docx", "WC/WC041-Table-5-Mod.docx", 2)]
+ [InlineData("WC-1840", "WC/WC042-Table-5.docx", "WC/WC042-Table-5-Mod.docx", 2)]
+ [InlineData("WC-1850", "WC/WC043-Nested-Table.docx", "WC/WC043-Nested-Table-Mod.docx", 2)]
+ [InlineData("WC-1860", "WC/WC044-Text-Box.docx", "WC/WC044-Text-Box-Mod.docx", 2)]
+ [InlineData("WC-1870", "WC/WC045-Text-Box.docx", "WC/WC045-Text-Box-Mod.docx", 2)]
+ [InlineData("WC-1880", "WC/WC046-Two-Text-Box.docx", "WC/WC046-Two-Text-Box-Mod.docx", 2)]
+ [InlineData("WC-1890", "WC/WC047-Two-Text-Box.docx", "WC/WC047-Two-Text-Box-Mod.docx", 2)]
+ [InlineData("WC-1900", "WC/WC048-Text-Box-in-Cell.docx", "WC/WC048-Text-Box-in-Cell-Mod.docx", 6)]
+ [InlineData("WC-1910", "WC/WC049-Text-Box-in-Cell.docx", "WC/WC049-Text-Box-in-Cell-Mod.docx", 5)]
+ [InlineData("WC-1920", "WC/WC050-Table-in-Text-Box.docx", "WC/WC050-Table-in-Text-Box-Mod.docx", 8)]
+ [InlineData("WC-1930", "WC/WC051-Table-in-Text-Box.docx", "WC/WC051-Table-in-Text-Box-Mod.docx", 9)]
+ [InlineData("WC-1940", "WC/WC052-SmartArt-Same.docx", "WC/WC052-SmartArt-Same-Mod.docx", 2)]
+ [InlineData("WC-1950", "WC/WC053-Text-in-Cell.docx", "WC/WC053-Text-in-Cell-Mod.docx", 2)]
+ [InlineData("WC-1960", "WC/WC054-Text-in-Cell.docx", "WC/WC054-Text-in-Cell-Mod.docx", 0)]
+ [InlineData("WC-1970", "WC/WC055-French.docx", "WC/WC055-French-Mod.docx", 2)]
+ [InlineData("WC-1980", "WC/WC056-French.docx", "WC/WC056-French-Mod.docx", 2)]
+ [InlineData("WC-1990", "WC/WC057-Table-Merged-Cell.docx", "WC/WC057-Table-Merged-Cell-Mod.docx", 4)]
+ [InlineData("WC-2000", "WC/WC058-Table-Merged-Cell.docx", "WC/WC058-Table-Merged-Cell-Mod.docx", 6)]
+ [InlineData("WC-2010", "WC/WC059-Footnote.docx", "WC/WC059-Footnote-Mod.docx", 5)]
+ [InlineData("WC-2020", "WC/WC060-Endnote.docx", "WC/WC060-Endnote-Mod.docx", 3)]
+ [InlineData("WC-2030", "WC/WC061-Style-Added.docx", "WC/WC061-Style-Added-Mod.docx", 1)]
+ [InlineData("WC-2040", "WC/WC062-New-Char-Style-Added.docx", "WC/WC062-New-Char-Style-Added-Mod.docx", 2)]
+ [InlineData("WC-2050", "WC/WC063-Footnote.docx", "WC/WC063-Footnote-Mod.docx", 1)]
+ [InlineData("WC-2060", "WC/WC063-Footnote-Mod.docx", "WC/WC063-Footnote.docx", 1)]
+ [InlineData("WC-2070", "WC/WC064-Footnote.docx", "WC/WC064-Footnote-Mod.docx", 0)]
+ [InlineData("WC-2080", "WC/WC065-Textbox.docx", "WC/WC065-Textbox-Mod.docx", 2)]
+ [InlineData("WC-2090", "WC/WC066-Textbox-Before-Ins.docx", "WC/WC066-Textbox-Before-Ins-Mod.docx", 1)]
+ [InlineData("WC-2092", "WC/WC066-Textbox-Before-Ins-Mod.docx", "WC/WC066-Textbox-Before-Ins.docx", 1)]
+ [InlineData("WC-2100", "WC/WC067-Textbox-Image.docx", "WC/WC067-Textbox-Image-Mod.docx", 2)]
+ //[InlineData("WC-1000", "", "", 0)]
+ //[InlineData("WC-1000", "", "", 0)]
+ //[InlineData("WC-1000", "", "", 0)]
+ //[InlineData("WC-1000", "", "", 0)]
+ //[InlineData("WC-1000", "", "", 0)]
+ //[InlineData("WC-1000", "", "", 0)]
+
+ public void WC003_Compare(string testId, string name1, string name2, int revisionCount)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id???");
+ else
+ thisTestTempDir.Create();
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name));
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ //var baselineDocxWithRevisionsFi = new FileInfo(Path.Combine(source1Docx.DirectoryName, before + "-COMPARE-" + after + ".docx"));
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ var path = new DirectoryInfo(@"C:\Users\Eric\Documents\WindowsPowerShellModules\Open-Xml-PowerTools\TestFiles");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ /************************************************************************************************************************/
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ settings.DebugTempFileDi = thisTestTempDir;
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxWithRevisionsFi.FullName);
+
+#if false
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // create batch file to copy newly generated ContentTypeXml and NarrDoc to the TestFiles directory.
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var batchFileName = "Copy-Gen-Files-To-TestFiles.bat";
+ var batchFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, batchFileName));
+ var batch = "";
+ batch += "copy " + docxWithRevisionsFi.FullName + " " + source1Docx.DirectoryName + Environment.NewLine;
+ if (batchFi.Exists)
+ File.AppendAllText(batchFi.FullName, batch);
+ else
+ File.WriteAllText(batchFi.FullName, batch);
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+#endif
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // validate generated document
+ var validationErrors = "";
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(comparedWml.DocumentByteArray, 0, comparedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+ }
+ validationErrors = sb.ToString();
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxWithRevisionsFi);
+ }
+
+ /************************************************************************************************************************/
+
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Open Windows Explorer
+ if (m_OpenTempDirInExplorer)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var semaphorFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "z_ExplorerOpenedSemaphore.txt"));
+ if (!semaphorFi.Exists)
+ {
+ File.WriteAllText(semaphorFi.FullName, "");
+ TestUtil.Explorer(thisTestTempDir);
+ }
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ if (validationErrors != "")
+ {
+ Assert.True(false, validationErrors);
+ }
+
+ WmlComparerSettings settings2 = new WmlComparerSettings();
+
+ WmlDocument revisionWml = new WmlDocument(docxWithRevisionsFi.FullName);
+ var revisions = WmlComparer.GetRevisions(revisionWml, settings);
+ Assert.Equal(revisionCount, revisions.Count());
+
+ var afterRejectingWml = RevisionProcessor.RejectRevisions(revisionWml);
+
+ var WRITE_TEMP_FILES = true;
+
+ if (WRITE_TEMP_FILES)
+ {
+ var afterRejectingFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, "AfterRejecting.docx"));
+ afterRejectingWml.SaveAs(afterRejectingFi.FullName);
+ }
+
+ WmlDocument afterRejectingComparedWml = WmlComparer.Compare(source1Wml, afterRejectingWml, settings);
+ var sanityCheck1 = WmlComparer.GetRevisions(afterRejectingComparedWml, settings);
+
+ if (WRITE_TEMP_FILES)
+ {
+ var afterRejectingComparedFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, "AfterRejectingCompared.docx"));
+ afterRejectingComparedWml.SaveAs(afterRejectingComparedFi.FullName);
+ }
+
+ var afterAcceptingWml = RevisionProcessor.AcceptRevisions(revisionWml);
+
+ if (WRITE_TEMP_FILES)
+ {
+ var afterAcceptingFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, "AfterAccepting.docx"));
+ afterAcceptingWml.SaveAs(afterAcceptingFi.FullName);
+ }
+
+ WmlDocument afterAcceptingComparedWml = WmlComparer.Compare(source2Wml, afterAcceptingWml, settings);
+ var sanityCheck2 = WmlComparer.GetRevisions(afterAcceptingComparedWml, settings);
+
+ if (WRITE_TEMP_FILES)
+ {
+ var afterAcceptingComparedFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, "AfterAcceptingCompared.docx"));
+ afterAcceptingComparedWml.SaveAs(afterAcceptingComparedFi.FullName);
+ }
+
+ if (sanityCheck1.Count() != 0)
+ Assert.True(false, "Sanity Check #1 failed");
+ if (sanityCheck2.Count() != 0)
+ Assert.True(false, "Sanity Check #2 failed");
+ }
+
+#if false
+ [Theory]
+ [InlineData("WC/WC037-Textbox-Before.docx", "WC/WC037-Textbox-After1.docx", 2)]
+
+ public void WC003_Throws(string name1, string name2, int revisionCount)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ if (!source2CopiedToDestDocx.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ Assert.Throws<OpenXmlPowerToolsException>(() =>
+ {
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ });
+ }
+#endif
+
+ [Theory]
+ [InlineData("WCS-1000", "WC/WC001-Digits.docx")]
+ [InlineData("WCS-1010", "WC/WC001-Digits-Deleted-Paragraph.docx")]
+ [InlineData("WCS-1020", "WC/WC001-Digits-Mod.docx")]
+ [InlineData("WCS-1030", "WC/WC002-DeleteAtBeginning.docx")]
+ [InlineData("WCS-1040", "WC/WC002-DeleteAtEnd.docx")]
+ [InlineData("WCS-1050", "WC/WC002-DeleteInMiddle.docx")]
+ [InlineData("WCS-1060", "WC/WC002-DiffAtBeginning.docx")]
+ [InlineData("WCS-1070", "WC/WC002-DiffInMiddle.docx")]
+ [InlineData("WCS-1080", "WC/WC002-InsertAtBeginning.docx")]
+ [InlineData("WCS-1090", "WC/WC002-InsertAtEnd.docx")]
+ [InlineData("WCS-1100", "WC/WC002-InsertInMiddle.docx")]
+ [InlineData("WCS-1110", "WC/WC002-Unmodified.docx")]
+ //[InlineData("WCS-1120", "WC/WC004-Large.docx")]
+ //[InlineData("WCS-1130", "WC/WC004-Large-Mod.docx")]
+ [InlineData("WCS-1140", "WC/WC006-Table.docx")]
+ [InlineData("WCS-1150", "WC/WC006-Table-Delete-Contests-of-Row.docx")]
+ [InlineData("WCS-1160", "WC/WC006-Table-Delete-Row.docx")]
+ [InlineData("WCS-1170", "WC/WC007-Deleted-at-Beginning-of-Para.docx")]
+ [InlineData("WCS-1180", "WC/WC007-Longest-At-End.docx")]
+ [InlineData("WCS-1190", "WC/WC007-Moved-into-Table.docx")]
+ [InlineData("WCS-1200", "WC/WC007-Unmodified.docx")]
+ [InlineData("WCS-1210", "WC/WC009-Table-Cell-1-1-Mod.docx")]
+ [InlineData("WCS-1220", "WC/WC009-Table-Unmodified.docx")]
+ [InlineData("WCS-1230", "WC/WC010-Para-Before-Table-Mod.docx")]
+ [InlineData("WCS-1240", "WC/WC010-Para-Before-Table-Unmodified.docx")]
+ [InlineData("WCS-1250", "WC/WC011-After.docx")]
+ [InlineData("WCS-1260", "WC/WC011-Before.docx")]
+ [InlineData("WCS-1270", "WC/WC012-Math-After.docx")]
+ [InlineData("WCS-1280", "WC/WC012-Math-Before.docx")]
+ [InlineData("WCS-1290", "WC/WC013-Image-After.docx")]
+ [InlineData("WCS-1300", "WC/WC013-Image-After2.docx")]
+ [InlineData("WCS-1310", "WC/WC013-Image-Before.docx")]
+ [InlineData("WCS-1320", "WC/WC013-Image-Before2.docx")]
+ [InlineData("WCS-1330", "WC/WC014-SmartArt-After.docx")]
+ [InlineData("WCS-1340", "WC/WC014-SmartArt-Before.docx")]
+ [InlineData("WCS-1350", "WC/WC014-SmartArt-With-Image-After.docx")]
+ [InlineData("WCS-1360", "WC/WC014-SmartArt-With-Image-Before.docx")]
+ [InlineData("WCS-1370", "WC/WC014-SmartArt-With-Image-Deleted-After.docx")]
+ [InlineData("WCS-1380", "WC/WC014-SmartArt-With-Image-Deleted-After2.docx")]
+ [InlineData("WCS-1390", "WC/WC015-Three-Paragraphs.docx")]
+ [InlineData("WCS-1400", "WC/WC015-Three-Paragraphs-After.docx")]
+ [InlineData("WCS-1410", "WC/WC016-Para-Image-Para.docx")]
+ [InlineData("WCS-1420", "WC/WC016-Para-Image-Para-w-Deleted-Image.docx")]
+ [InlineData("WCS-1430", "WC/WC017-Image.docx")]
+ [InlineData("WCS-1440", "WC/WC017-Image-After.docx")]
+ [InlineData("WCS-1450", "WC/WC018-Field-Simple-After-1.docx")]
+ [InlineData("WCS-1460", "WC/WC018-Field-Simple-After-2.docx")]
+ [InlineData("WCS-1470", "WC/WC018-Field-Simple-Before.docx")]
+ [InlineData("WCS-1480", "WC/WC019-Hyperlink-After-1.docx")]
+ [InlineData("WCS-1490", "WC/WC019-Hyperlink-After-2.docx")]
+ [InlineData("WCS-1500", "WC/WC019-Hyperlink-Before.docx")]
+ [InlineData("WCS-1510", "WC/WC020-FootNote-After-1.docx")]
+ [InlineData("WCS-1520", "WC/WC020-FootNote-After-2.docx")]
+ [InlineData("WCS-1530", "WC/WC020-FootNote-Before.docx")]
+ [InlineData("WCS-1540", "WC/WC021-Math-After-1.docx")]
+ [InlineData("WCS-1550", "WC/WC021-Math-Before-1.docx")]
+ [InlineData("WCS-1560", "WC/WC022-Image-Math-Para-After.docx")]
+ [InlineData("WCS-1570", "WC/WC022-Image-Math-Para-Before.docx")]
+ //[InlineData("WCS-1580", "", "")]
+ //[InlineData("WCS-1590", "", "")]
+ //[InlineData("WCS-1600", "", "")]
+ //[InlineData("WCS-1610", "", "")]
+
+ public void WC004_Compare_To_Self(string testId, string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id???");
+ else
+ thisTestTempDir.Create();
+
+ var sourceCopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, sourceDocx.Name.Replace(".docx", "-Source.docx")));
+ if (!sourceCopiedToDestDocx.Exists)
+ File.Copy(sourceDocx.FullName, sourceCopiedToDestDocx.FullName);
+
+ var before = sourceCopiedToDestDocx.Name.Replace(".docx", "");
+ var docxComparedFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE" + ".docx"));
+ var docxCompared2Fi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE2" + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(sourceCopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(sourceCopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxComparedFi.FullName);
+ ValidateDocument(comparedWml);
+
+ WmlDocument comparedWml2 = WmlComparer.Compare(comparedWml, source1Wml, settings);
+ comparedWml2.SaveAs(docxCompared2Fi.FullName);
+ ValidateDocument(comparedWml2);
+ }
+
+ [Theory]
+ [InlineData("WCI-1000", "WC/WC040-Case-Before.docx", "WC/WC040-Case-After.docx", 2)]
+
+ public void WC005_Compare_CaseInsensitive(string testId, string name1, string name2, int revisionCount)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id???");
+ else
+ thisTestTempDir.Create();
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ if (!source2CopiedToDestDocx.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ if (!source1CopiedToDestDocxForWord.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ if (!source2CopiedToDestDocxForWord.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ var path = new DirectoryInfo(@"C:\Users\Eric\Documents\WindowsPowerShellModules\Open-Xml-PowerTools\TestFiles");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ /************************************************************************************************************************/
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ settings.CaseInsensitive = true;
+ settings.CultureInfo = System.Globalization.CultureInfo.CurrentCulture;
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxWithRevisionsFi.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(comparedWml.DocumentByteArray, 0, comparedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+ }
+ var sbs = sb.ToString();
+ Assert.Equal("", sbs);
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxWithRevisionsFi);
+ }
+
+ /************************************************************************************************************************/
+
+ WmlDocument revisionWml = new WmlDocument(docxWithRevisionsFi.FullName);
+ var revisions = WmlComparer.GetRevisions(revisionWml, settings);
+ Assert.Equal(revisionCount, revisions.Count());
+ }
+
+ private static void ValidateDocument(WmlDocument wmlToValidate)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlToValidate.DocumentByteArray, 0, wmlToValidate.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() != 0)
+ {
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+
+ }
+ var sbs = sb.ToString();
+ Assert.Equal("", sbs);
+ }
+ }
+ }
+ }
+
+ public static string[] ExpectedErrors = new string[] {
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noVBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:allStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:customStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:latentStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:stylesInUse' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:headingStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:numberingStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:tableStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnRuns' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnParagraphs' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnNumbering' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnTables' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:clearFormatting' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:top3HeadingStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:visibleStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:alternateStyleNames' attribute is not declared.",
+ "The attribute 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:val' has invalid value '0'. The MinInclusive constraint failed. The value must be greater than or equal to 1.",
+ "The attribute 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:val' has invalid value '0'. The MinInclusive constraint failed. The value must be greater than or equal to 2.",
+ "The 'urn:schemas-microsoft-com:office:office:gfxdata' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:fill' attribute is invalid - The value '0' is not valid according to any of the memberTypes of the union.",
+ };
+
+ }
+
+ public class WordRunner
+ {
+ public static void RunWord(FileInfo executablePath, FileInfo docxPath)
+ {
+ if (executablePath.Exists)
+ {
+ using (Process proc = new Process())
+ {
+ proc.StartInfo.FileName = executablePath.FullName;
+ proc.StartInfo.Arguments = docxPath.FullName;
+ proc.StartInfo.WorkingDirectory = docxPath.DirectoryName;
+ proc.StartInfo.UseShellExecute = false;
+ proc.StartInfo.RedirectStandardOutput = true;
+ proc.StartInfo.RedirectStandardError = true;
+ proc.Start();
+ }
+ }
+ else
+ {
+ throw new ArgumentException("Invalid executable path.", "executablePath");
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/WmlComparerTests2.cs b/OpenXmlPowerTools.Tests/WmlComparerTests2.cs
new file mode 100644
index 0000000..1038d7c
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/WmlComparerTests2.cs
@@ -0,0 +1,1002 @@
+/***************************************************************************
+
+Copyright (c) Eric White 2016.
+
+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
+
+Published at http://EricWhite.com
+Resource Center and Documentation: http://ericwhite.com/blog/blog/open-xml-powertools-developer-center/
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using Xunit;
+using System.Diagnostics;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class WcTests2
+ {
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ public static bool m_OpenWord = false;
+ public static bool m_OpenTempDirInExplorer = false;
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ [Theory]
+ [InlineData("CZ-1000", "CZ/CZ001-Plain.docx", "CZ/CZ001-Plain-Mod.docx", 1)]
+ [InlineData("CZ-1010", "CZ/CZ002-Multi-Paragraphs.docx", "CZ/CZ002-Multi-Paragraphs-Mod.docx", 1)]
+ [InlineData("CZ-1020", "CZ/CZ003-Multi-Paragraphs.docx", "CZ/CZ003-Multi-Paragraphs-Mod.docx", 1)]
+ [InlineData("CZ-1030", "CZ/CZ004-Multi-Paragraphs-in-Cell.docx", "CZ/CZ004-Multi-Paragraphs-in-Cell-Mod.docx", 1)]
+ public void CZ001_CompareTrackedInPrev(string testId, string name1, string name2, int revisionCount)
+ {
+ // TODO: Do we need to keep the revision count parameter?
+ Assert.Equal(1, revisionCount);
+
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (thisTestTempDir.Exists)
+ Assert.True(false, "Duplicate test id???");
+ else
+ thisTestTempDir.Create();
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name));
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ if (m_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ if (!source1CopiedToDestDocxForWord.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ if (!source2CopiedToDestDocxForWord.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ settings.DebugTempFileDi = thisTestTempDir;
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+
+ ///////////////////////////
+ comparedWml.SaveAs(docxWithRevisionsFi.FullName);
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(comparedWml.DocumentByteArray, 0, comparedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ if (sbs != "")
+ Assert.True(false, sbs.ToString());
+ }
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ if (m_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxWithRevisionsFi);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Open Windows Explorer
+ if (m_OpenTempDirInExplorer)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var semaphorFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "z_ExplorerOpenedSemaphore.txt"));
+ if (!semaphorFi.Exists)
+ {
+ File.WriteAllText(semaphorFi.FullName, "");
+ TestUtil.Explorer(thisTestTempDir);
+ }
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+#if false
+ WmlDocument revisionWml = new WmlDocument(docxWithRevisionsFi.FullName);
+ var revisions = WmlComparer.GetRevisions(revisionWml, settings);
+ Assert.Equal(revisionCount, revisions.Count());
+#endif
+ }
+
+#if false
+ [Theory]
+ [InlineData("CZ-2000", "CA001-Plain.docx", "CA001-Plain-Mod.docx", 1)]
+ [InlineData("CZ-2010", "WC001-Digits.docx", "WC001-Digits-Mod.docx", 4)]
+ [InlineData("CZ-2020", "WC001-Digits.docx", "WC001-Digits-Deleted-Paragraph.docx", 1)]
+ [InlineData("CZ-2030", "WC001-Digits-Deleted-Paragraph.docx", "WC001-Digits.docx", 1)]
+ [InlineData("CZ-2040", "WC002-Unmodified.docx", "WC002-DiffInMiddle.docx", 2)]
+ [InlineData("CZ-2050", "WC002-Unmodified.docx", "WC002-DiffAtBeginning.docx", 2)]
+ [InlineData("CZ-2060", "WC002-Unmodified.docx", "WC002-DeleteAtBeginning.docx", 1)]
+ [InlineData("CZ-2070", "WC002-Unmodified.docx", "WC002-InsertAtBeginning.docx", 1)]
+ [InlineData("CZ-2080", "WC002-Unmodified.docx", "WC002-InsertAtEnd.docx", 1)]
+ [InlineData("CZ-2080", "WC002-Unmodified.docx", "WC002-DeleteAtEnd.docx", 1)]
+ [InlineData("CZ-2100", "WC002-Unmodified.docx", "WC002-DeleteInMiddle.docx", 1)]
+ [InlineData("CZ-2110", "WC002-Unmodified.docx", "WC002-InsertInMiddle.docx", 1)]
+ [InlineData("CZ-2120", "WC002-DeleteInMiddle.docx", "WC002-Unmodified.docx", 1)]
+ //[InlineData("CZ-2130", "WC004-Large.docx", "WC004-Large-Mod.docx", 2)]
+ [InlineData("CZ-2140", "WC006-Table.docx", "WC006-Table-Delete-Row.docx", 1)]
+ [InlineData("CZ-2150", "WC006-Table-Delete-Row.docx", "WC006-Table.docx", 1)]
+ [InlineData("CZ-2160", "WC006-Table.docx", "WC006-Table-Delete-Contests-of-Row.docx", 2)]
+ [InlineData("CZ-2170", "WC007-Unmodified.docx", "WC007-Longest-At-End.docx", 2)]
+ [InlineData("CZ-2180", "WC007-Unmodified.docx", "WC007-Deleted-at-Beginning-of-Para.docx", 2)]
+ [InlineData("CZ-2200", "WC007-Unmodified.docx", "WC007-Moved-into-Table.docx", 2)]
+ [InlineData("CZ-2210", "WC009-Table-Unmodified.docx", "WC009-Table-Cell-1-1-Mod.docx", 1)]
+ [InlineData("CZ-2220", "WC010-Para-Before-Table-Unmodified.docx", "WC010-Para-Before-Table-Mod.docx", 3)]
+ [InlineData("CZ-2230", "WC011-Before.docx", "WC011-After.docx", 2)]
+ [InlineData("CZ-2240", "WC012-Math-Before.docx", "WC012-Math-After.docx", 2)]
+ [InlineData("CZ-2250", "WC013-Image-Before.docx", "WC013-Image-After.docx", 2)]
+ [InlineData("CZ-2260", "WC013-Image-Before.docx", "WC013-Image-After2.docx", 2)]
+ [InlineData("CZ-2270", "WC013-Image-Before2.docx", "WC013-Image-After2.docx", 2)]
+ [InlineData("CZ-2280", "WC014-SmartArt-Before.docx", "WC014-SmartArt-After.docx", 2)]
+ [InlineData("CZ-2300", "WC014-SmartArt-With-Image-Before.docx", "WC014-SmartArt-With-Image-After.docx", 2)]
+ [InlineData("CZ-2310", "WC014-SmartArt-With-Image-Before.docx", "WC014-SmartArt-With-Image-Deleted-After.docx", 3)]
+ [InlineData("CZ-2320", "WC014-SmartArt-With-Image-Before.docx", "WC014-SmartArt-With-Image-Deleted-After2.docx", 1)]
+ [InlineData("CZ-2330", "WC015-Three-Paragraphs.docx", "WC015-Three-Paragraphs-After.docx", 3)]
+ [InlineData("CZ-2340", "WC016-Para-Image-Para.docx", "WC016-Para-Image-Para-w-Deleted-Image.docx", 1)]
+ [InlineData("CZ-2350", "WC017-Image.docx", "WC017-Image-After.docx", 3)]
+ [InlineData("CZ-2360", "WC018-Field-Simple-Before.docx", "WC018-Field-Simple-After-1.docx", 2)]
+ [InlineData("CZ-2370", "WC018-Field-Simple-Before.docx", "WC018-Field-Simple-After-2.docx", 3)]
+ [InlineData("CZ-2380", "WC019-Hyperlink-Before.docx", "WC019-Hyperlink-After-1.docx", 3)]
+ [InlineData("CZ-2400", "WC019-Hyperlink-Before.docx", "WC019-Hyperlink-After-2.docx", 5)]
+ [InlineData("CZ-2410", "WC020-FootNote-Before.docx", "WC020-FootNote-After-1.docx", 3)]
+ [InlineData("CZ-2420", "WC020-FootNote-Before.docx", "WC020-FootNote-After-2.docx", 5)]
+ [InlineData("CZ-2430", "WC021-Math-Before-1.docx", "WC021-Math-After-1.docx", 9)]
+ [InlineData("CZ-2440", "WC021-Math-Before-2.docx", "WC021-Math-After-2.docx", 6)]
+ [InlineData("CZ-2450", "WC022-Image-Math-Para-Before.docx", "WC022-Image-Math-Para-After.docx", 22)]
+ [InlineData("CZ-2460", "WC023-Table-4-Row-Image-Before.docx", "WC023-Table-4-Row-Image-After-Delete-1-Row.docx", 9)]
+ [InlineData("CZ-2470", "WC024-Table-Before.docx", "WC024-Table-After.docx", 1)]
+ [InlineData("CZ-2480", "WC024-Table-Before.docx", "WC024-Table-After2.docx", 7)]
+ [InlineData("CZ-2500", "WC025-Simple-Table-Before.docx", "WC025-Simple-Table-After.docx", 4)]
+ [InlineData("CZ-2510", "WC026-Long-Table-Before.docx", "WC026-Long-Table-After-1.docx", 2)]
+ [InlineData("CZ-2520", "WC027-Twenty-Paras-Before.docx", "WC027-Twenty-Paras-After-1.docx", 2)]
+ [InlineData("CZ-2530", "WC027-Twenty-Paras-After-1.docx", "WC027-Twenty-Paras-Before.docx", 2)]
+ [InlineData("CZ-2540", "WC027-Twenty-Paras-Before.docx", "WC027-Twenty-Paras-After-2.docx", 4)]
+ [InlineData("CZ-2550", "WC030-Image-Math-Before.docx", "WC030-Image-Math-After.docx", 2)]
+ [InlineData("CZ-2560", "WC031-Two-Maths-Before.docx", "WC031-Two-Maths-After.docx", 4)]
+ [InlineData("CZ-2570", "WC032-Para-with-Para-Props.docx", "WC032-Para-with-Para-Props-After.docx", 3)]
+ [InlineData("CZ-2580", "WC033-Merged-Cells-Before.docx", "WC033-Merged-Cells-After1.docx", 2)]
+ [InlineData("CZ-2600", "WC033-Merged-Cells-Before.docx", "WC033-Merged-Cells-After2.docx", 4)]
+ [InlineData("CZ-2610", "WC034-Footnotes-Before.docx", "WC034-Footnotes-After1.docx", 1)]
+ [InlineData("CZ-2620", "WC034-Footnotes-Before.docx", "WC034-Footnotes-After2.docx", 6)]
+ [InlineData("CZ-2630", "WC034-Footnotes-Before.docx", "WC034-Footnotes-After3.docx", 3)]
+ [InlineData("CZ-2640", "WC034-Footnotes-After3.docx", "WC034-Footnotes-Before.docx", 3)]
+ [InlineData("CZ-2650", "WC035-Footnote-Before.docx", "WC035-Footnote-After.docx", 2)]
+ [InlineData("CZ-2660", "WC035-Footnote-After.docx", "WC035-Footnote-Before.docx", 2)]
+ [InlineData("CZ-2670", "WC036-Footnote-With-Table-Before.docx", "WC036-Footnote-With-Table-After.docx", 5)]
+ [InlineData("CZ-2680", "WC036-Footnote-With-Table-After.docx", "WC036-Footnote-With-Table-Before.docx", 5)]
+ [InlineData("CZ-2700", "WC034-Endnotes-Before.docx", "WC034-Endnotes-After1.docx", 1)]
+ [InlineData("CZ-2710", "WC034-Endnotes-Before.docx", "WC034-Endnotes-After2.docx", 6)]
+ [InlineData("CZ-2720", "WC034-Endnotes-Before.docx", "WC034-Endnotes-After3.docx", 8)]
+ [InlineData("CZ-2730", "WC034-Endnotes-After3.docx", "WC034-Endnotes-Before.docx", 8)]
+ [InlineData("CZ-2740", "WC035-Endnote-Before.docx", "WC035-Endnote-After.docx", 2)]
+ [InlineData("CZ-2750", "WC035-Endnote-After.docx", "WC035-Endnote-Before.docx", 2)]
+ [InlineData("CZ-2760", "WC036-Endnote-With-Table-Before.docx", "WC036-Endnote-With-Table-After.docx", 6)]
+ [InlineData("CZ-2770", "WC036-Endnote-With-Table-After.docx", "WC036-Endnote-With-Table-Before.docx", 6)]
+ [InlineData("CZ-2780", "WC038-Document-With-BR-Before.docx", "WC038-Document-With-BR-After.docx", 2)]
+ [InlineData("CZ-2790", "RC001-Before.docx", "RC001-After1.docx", 2)]
+ [InlineData("CZ-2800", "RC002-Image.docx", "RC002-Image-After1.docx", 1)]
+ [InlineData("CZ-2810", "WC039-Break-In-Row.docx", "WC039-Break-In-Row-After1.docx", 1)]
+ //[InlineData("CZ-2820", "", "", 0)]
+ //[InlineData("CZ-2830", "", "", 0)]
+ //[InlineData("CZ-2840", "", "", 0)]
+ //[InlineData("CZ-2850", "", "", 0)]
+ //[InlineData("CZ-2860", "", "", 0)]
+ //[InlineData("CZ-2870", "", "", 0)]
+ //[InlineData("CZ-2880", "", "", 0)]
+ //[InlineData("CZ-2890", "", "", 0)]
+ public void CZ002_Compare(string testId, string name1, string name2, int revisionCount)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var rootTempDir = TestUtil.TempDir;
+ var thisTestTempDir = new DirectoryInfo(Path.Combine(rootTempDir.FullName, testId));
+ if (!thisTestTempDir.Exists)
+ thisTestTempDir.Create();
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ if (!source2CopiedToDestDocx.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ /************************************************************************************************************************/
+
+ if (m_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(thisTestTempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ if (!source1CopiedToDestDocxForWord.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ if (!source2CopiedToDestDocxForWord.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ /************************************************************************************************************************/
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(thisTestTempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ settings.DebugTempFileDi = TestUtil.TempDir;
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxWithRevisionsFi.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(comparedWml.DocumentByteArray, 0, comparedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ if (sbs != "")
+ Assert.True(false, sbs.ToString());
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (m_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxWithRevisionsFi);
+ }
+
+ /************************************************************************************************************************/
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // Open Windows Explorer
+ if (m_OpenTempDirInExplorer)
+ {
+ while (true)
+ {
+ try
+ {
+ ////////// CODE TO REPEAT UNTIL SUCCESS //////////
+ var semaphorFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, "z_ExplorerOpenedSemaphore.txt"));
+ if (!semaphorFi.Exists)
+ {
+ File.WriteAllText(semaphorFi.FullName, "");
+ TestUtil.Explorer(thisTestTempDir);
+ }
+ //////////////////////////////////////////////////
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+#if false
+ WmlDocument revisionWml = new WmlDocument(docxWithRevisionsFi.FullName);
+ var revisions = WmlComparer.GetRevisions(revisionWml, settings);
+ Assert.Equal(revisionCount, revisions.Count());
+#endif
+ }
+#endif
+
+#if false
+ [Theory]
+ [InlineData("RC001-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC001-After1.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ <RcInfo>
+ <DocName>RC001-After2.docx</DocName>
+ <Color>LightPink</Color>
+ <Revisor>From Fred</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC002-Image.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC002-Image-After1.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC002-Image-After1.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC002-Image.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("WC027-Twenty-Paras-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>WC027-Twenty-Paras-After-1.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("WC027-Twenty-Paras-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>WC027-Twenty-Paras-After-3.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC003-Multi-Paras.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC003-Multi-Paras-After.docx</DocName>
+ <Color>LightBlue</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ </Root>")]
+ [InlineData("RC004-Before.docx",
+ @"<Root>
+ <RcInfo>
+ <DocName>RC004-After1.docx</DocName>
+ <Color>LightYellow</Color>
+ <Revisor>From Bob</Revisor>
+ </RcInfo>
+ <RcInfo>
+ <DocName>RC004-After2.docx</DocName>
+ <Color>LightPink</Color>
+ <Revisor>From Fred</Revisor>
+ </RcInfo>
+ </Root>")]
+
+ public void WC001_Consolidate(string originalName, string revisedDocumentsXml)
+ {
+ FileInfo originalDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, originalName));
+
+ var originalCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, originalDocx.Name));
+ if (!originalCopiedToDestDocx.Exists)
+ File.Copy(originalDocx.FullName, originalCopiedToDestDocx.FullName);
+
+ var revisedDocumentsXElement = XElement.Parse(revisedDocumentsXml);
+ var revisedDocumentsArray = revisedDocumentsXElement
+ .Elements()
+ .Select(z =>
+ {
+ FileInfo revisedDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, z.Element("DocName").Value));
+ var revisedCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, revisedDocx.Name));
+ if (!revisedCopiedToDestDocx.Exists)
+ File.Copy(revisedDocx.FullName, revisedCopiedToDestDocx.FullName);
+ return new WmlRevisedDocumentInfo()
+ {
+ RevisedDocument = new WmlDocument(revisedCopiedToDestDocx.FullName),
+ Color = Color.FromName(z.Element("Color").Value),
+ Revisor = z.Element("Revisor").Value,
+ };
+ })
+ .ToList();
+
+ var consolidatedDocxName = originalCopiedToDestDocx.Name.Replace(".docx", "-Consolidated.docx");
+ var consolidatedDocumentFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, consolidatedDocxName));
+
+ WmlDocument source1Wml = new WmlDocument(originalCopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ WmlDocument consolidatedWml = WmlComparer.Consolidate(
+ source1Wml,
+ revisedDocumentsArray,
+ settings);
+ consolidatedWml.SaveAs(consolidatedDocumentFi.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(consolidatedWml.DocumentByteArray, 0, consolidatedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+ }
+ var sbs = sb.ToString();
+ Assert.Equal("", sbs);
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, consolidatedDocumentFi);
+ }
+
+ /************************************************************************************************************************/
+ }
+
+ [Theory]
+ [InlineData("CA001-Plain.docx", "CA001-Plain-Mod.docx")]
+ [InlineData("WC001-Digits.docx", "WC001-Digits-Mod.docx")]
+ [InlineData("WC001-Digits.docx", "WC001-Digits-Deleted-Paragraph.docx")]
+ [InlineData("WC001-Digits-Deleted-Paragraph.docx", "WC001-Digits.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-DiffInMiddle.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-DiffAtBeginning.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-DeleteAtBeginning.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-InsertAtBeginning.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-InsertAtEnd.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-DeleteAtEnd.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-DeleteInMiddle.docx")]
+ [InlineData("WC002-Unmodified.docx", "WC002-InsertInMiddle.docx")]
+ [InlineData("WC002-DeleteInMiddle.docx", "WC002-Unmodified.docx")]
+ //[InlineData("WC004-Large.docx", "WC004-Large-Mod.docx")]
+ [InlineData("WC006-Table.docx", "WC006-Table-Delete-Row.docx")]
+ [InlineData("WC006-Table-Delete-Row.docx", "WC006-Table.docx")]
+ [InlineData("WC006-Table.docx", "WC006-Table-Delete-Contests-of-Row.docx")]
+ [InlineData("WC007-Unmodified.docx", "WC007-Longest-At-End.docx")]
+ [InlineData("WC007-Unmodified.docx", "WC007-Deleted-at-Beginning-of-Para.docx")]
+ [InlineData("WC007-Unmodified.docx", "WC007-Moved-into-Table.docx")]
+ [InlineData("WC009-Table-Unmodified.docx", "WC009-Table-Cell-1-1-Mod.docx")]
+ [InlineData("WC010-Para-Before-Table-Unmodified.docx", "WC010-Para-Before-Table-Mod.docx")]
+ [InlineData("WC011-Before.docx", "WC011-After.docx")]
+ [InlineData("WC012-Math-Before.docx", "WC012-Math-After.docx")]
+ [InlineData("WC013-Image-Before.docx", "WC013-Image-After.docx")]
+ [InlineData("WC013-Image-Before.docx", "WC013-Image-After2.docx")]
+ [InlineData("WC013-Image-Before2.docx", "WC013-Image-After2.docx")]
+ [InlineData("WC014-SmartArt-Before.docx", "WC014-SmartArt-After.docx")]
+ [InlineData("WC014-SmartArt-With-Image-Before.docx", "WC014-SmartArt-With-Image-After.docx")]
+ [InlineData("WC014-SmartArt-With-Image-Before.docx", "WC014-SmartArt-With-Image-Deleted-After.docx")]
+ [InlineData("WC014-SmartArt-With-Image-Before.docx", "WC014-SmartArt-With-Image-Deleted-After2.docx")]
+ [InlineData("WC015-Three-Paragraphs.docx", "WC015-Three-Paragraphs-After.docx")]
+ [InlineData("WC016-Para-Image-Para.docx", "WC016-Para-Image-Para-w-Deleted-Image.docx")]
+ [InlineData("WC017-Image.docx", "WC017-Image-After.docx")]
+ [InlineData("WC018-Field-Simple-Before.docx", "WC018-Field-Simple-After-1.docx")]
+ [InlineData("WC018-Field-Simple-Before.docx", "WC018-Field-Simple-After-2.docx")]
+ [InlineData("WC019-Hyperlink-Before.docx", "WC019-Hyperlink-After-1.docx")]
+ [InlineData("WC019-Hyperlink-Before.docx", "WC019-Hyperlink-After-2.docx")]
+ [InlineData("WC020-FootNote-Before.docx", "WC020-FootNote-After-1.docx")]
+ [InlineData("WC020-FootNote-Before.docx", "WC020-FootNote-After-2.docx")]
+ [InlineData("WC021-Math-Before-1.docx", "WC021-Math-After-1.docx")]
+ [InlineData("WC021-Math-Before-2.docx", "WC021-Math-After-2.docx")]
+ [InlineData("WC022-Image-Math-Para-Before.docx", "WC022-Image-Math-Para-After.docx")]
+ [InlineData("WC023-Table-4-Row-Image-Before.docx", "WC023-Table-4-Row-Image-After-Delete-1-Row.docx")]
+ [InlineData("WC024-Table-Before.docx", "WC024-Table-After.docx")]
+ [InlineData("WC024-Table-Before.docx", "WC024-Table-After2.docx")]
+ [InlineData("WC025-Simple-Table-Before.docx", "WC025-Simple-Table-After.docx")]
+ [InlineData("WC026-Long-Table-Before.docx", "WC026-Long-Table-After-1.docx")]
+ [InlineData("WC027-Twenty-Paras-Before.docx", "WC027-Twenty-Paras-After-1.docx")]
+ [InlineData("WC027-Twenty-Paras-After-1.docx", "WC027-Twenty-Paras-Before.docx")]
+ [InlineData("WC027-Twenty-Paras-Before.docx", "WC027-Twenty-Paras-After-2.docx")]
+ [InlineData("WC030-Image-Math-Before.docx", "WC030-Image-Math-After.docx")]
+ [InlineData("WC031-Two-Maths-Before.docx", "WC031-Two-Maths-After.docx")]
+ [InlineData("WC032-Para-with-Para-Props.docx", "WC032-Para-with-Para-Props-After.docx")]
+ [InlineData("WC033-Merged-Cells-Before.docx", "WC033-Merged-Cells-After1.docx")]
+ [InlineData("WC033-Merged-Cells-Before.docx", "WC033-Merged-Cells-After2.docx")]
+ [InlineData("WC034-Footnotes-Before.docx", "WC034-Footnotes-After1.docx")]
+ [InlineData("WC034-Footnotes-Before.docx", "WC034-Footnotes-After2.docx")]
+ [InlineData("WC034-Footnotes-Before.docx", "WC034-Footnotes-After3.docx")]
+ [InlineData("WC034-Footnotes-After3.docx", "WC034-Footnotes-Before.docx")]
+ [InlineData("WC035-Footnote-Before.docx", "WC035-Footnote-After.docx")]
+ [InlineData("WC035-Footnote-After.docx", "WC035-Footnote-Before.docx")]
+ [InlineData("WC036-Footnote-With-Table-Before.docx", "WC036-Footnote-With-Table-After.docx")]
+ [InlineData("WC036-Footnote-With-Table-After.docx", "WC036-Footnote-With-Table-Before.docx")]
+ [InlineData("WC034-Endnotes-Before.docx", "WC034-Endnotes-After1.docx")]
+ [InlineData("WC034-Endnotes-Before.docx", "WC034-Endnotes-After2.docx")]
+ [InlineData("WC034-Endnotes-Before.docx", "WC034-Endnotes-After3.docx")]
+ [InlineData("WC034-Endnotes-After3.docx", "WC034-Endnotes-Before.docx")]
+ [InlineData("WC035-Endnote-Before.docx", "WC035-Endnote-After.docx")]
+ [InlineData("WC035-Endnote-After.docx", "WC035-Endnote-Before.docx")]
+ [InlineData("WC036-Endnote-With-Table-Before.docx", "WC036-Endnote-With-Table-After.docx")]
+ [InlineData("WC036-Endnote-With-Table-After.docx", "WC036-Endnote-With-Table-Before.docx")]
+ [InlineData("WC038-Document-With-BR-Before.docx", "WC038-Document-With-BR-After.docx")]
+ [InlineData("RC001-Before.docx", "RC001-After1.docx")]
+ [InlineData("RC002-Image.docx", "RC002-Image-After1.docx")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+
+
+ public void WC002_Consolidate_Bulk_Test(string name1, string name2)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ if (!source2CopiedToDestDocx.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ if (!source1CopiedToDestDocxForWord.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ if (!source2CopiedToDestDocxForWord.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ var path = new DirectoryInfo(@"C:\Users\Eric\Documents\WindowsPowerShellModules\Open-Xml-PowerTools\TestFiles");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ /************************************************************************************************************************/
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+ var docxConsolidatedFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, before + "-CONSOLIDATED-" + after + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxWithRevisionsFi.FullName);
+
+ List<WmlRevisedDocumentInfo> revisedDocInfo = new List<WmlRevisedDocumentInfo>()
+ {
+ new WmlRevisedDocumentInfo()
+ {
+ RevisedDocument = source2Wml,
+ Color = Color.LightBlue,
+ Revisor = "Revised by Eric White",
+ }
+ };
+ WmlDocument consolidatedWml = WmlComparer.Consolidate(
+ source1Wml,
+ revisedDocInfo,
+ settings);
+ consolidatedWml.SaveAs(docxConsolidatedFi.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(consolidatedWml.DocumentByteArray, 0, consolidatedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+#if true
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+#else
+ sb.Append(" \"" + err.Description + "\"," + Environment.NewLine);
+#endif
+ }
+ var sbs = sb.ToString();
+ Assert.Equal("", sbs);
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxConsolidatedFi);
+ }
+
+ /************************************************************************************************************************/
+ }
+#endif
+
+#if false
+ [Theory]
+ [InlineData("WC037-Textbox-Before.docx", "WC037-Textbox-After1.docx", 2)]
+
+ public void WC003_Throws(string name1, string name2, int revisionCount)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ if (!source2CopiedToDestDocx.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ Assert.Throws<OpenXmlPowerToolsException>(() =>
+ {
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ });
+ }
+
+ [Theory]
+ [InlineData("WC001-Digits.docx")]
+ [InlineData("WC001-Digits-Deleted-Paragraph.docx")]
+ [InlineData("WC001-Digits-Mod.docx")]
+ [InlineData("WC002-DeleteAtBeginning.docx")]
+ [InlineData("WC002-DeleteAtEnd.docx")]
+ [InlineData("WC002-DeleteInMiddle.docx")]
+ [InlineData("WC002-DiffAtBeginning.docx")]
+ [InlineData("WC002-DiffInMiddle.docx")]
+ [InlineData("WC002-InsertAtBeginning.docx")]
+ [InlineData("WC002-InsertAtEnd.docx")]
+ [InlineData("WC002-InsertInMiddle.docx")]
+ [InlineData("WC002-Unmodified.docx")]
+ //[InlineData("WC004-Large.docx")]
+ //[InlineData("WC004-Large-Mod.docx")]
+ [InlineData("WC006-Table.docx")]
+ [InlineData("WC006-Table-Delete-Contests-of-Row.docx")]
+ [InlineData("WC006-Table-Delete-Row.docx")]
+ [InlineData("WC007-Deleted-at-Beginning-of-Para.docx")]
+ [InlineData("WC007-Longest-At-End.docx")]
+ [InlineData("WC007-Moved-into-Table.docx")]
+ [InlineData("WC007-Unmodified.docx")]
+ [InlineData("WC009-Table-Cell-1-1-Mod.docx")]
+ [InlineData("WC009-Table-Unmodified.docx")]
+ [InlineData("WC010-Para-Before-Table-Mod.docx")]
+ [InlineData("WC010-Para-Before-Table-Unmodified.docx")]
+ [InlineData("WC011-After.docx")]
+ [InlineData("WC011-Before.docx")]
+ [InlineData("WC012-Math-After.docx")]
+ [InlineData("WC012-Math-Before.docx")]
+ [InlineData("WC013-Image-After.docx")]
+ [InlineData("WC013-Image-After2.docx")]
+ [InlineData("WC013-Image-Before.docx")]
+ [InlineData("WC013-Image-Before2.docx")]
+ [InlineData("WC014-SmartArt-After.docx")]
+ [InlineData("WC014-SmartArt-Before.docx")]
+ [InlineData("WC014-SmartArt-With-Image-After.docx")]
+ [InlineData("WC014-SmartArt-With-Image-Before.docx")]
+ [InlineData("WC014-SmartArt-With-Image-Deleted-After.docx")]
+ [InlineData("WC014-SmartArt-With-Image-Deleted-After2.docx")]
+ [InlineData("WC015-Three-Paragraphs.docx")]
+ [InlineData("WC015-Three-Paragraphs-After.docx")]
+ [InlineData("WC016-Para-Image-Para.docx")]
+ [InlineData("WC016-Para-Image-Para-w-Deleted-Image.docx")]
+ [InlineData("WC017-Image.docx")]
+ [InlineData("WC017-Image-After.docx")]
+ [InlineData("WC018-Field-Simple-After-1.docx")]
+ [InlineData("WC018-Field-Simple-After-2.docx")]
+ [InlineData("WC018-Field-Simple-Before.docx")]
+ [InlineData("WC019-Hyperlink-After-1.docx")]
+ [InlineData("WC019-Hyperlink-After-2.docx")]
+ [InlineData("WC019-Hyperlink-Before.docx")]
+ [InlineData("WC020-FootNote-After-1.docx")]
+ [InlineData("WC020-FootNote-After-2.docx")]
+ [InlineData("WC020-FootNote-Before.docx")]
+ [InlineData("WC021-Math-After-1.docx")]
+ [InlineData("WC021-Math-Before-1.docx")]
+ [InlineData("WC022-Image-Math-Para-After.docx")]
+ [InlineData("WC022-Image-Math-Para-Before.docx")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+ //[InlineData("", "")]
+
+ public void WC004_Compare_To_Self(string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var sourceCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-Source.docx")));
+ if (!sourceCopiedToDestDocx.Exists)
+ File.Copy(sourceDocx.FullName, sourceCopiedToDestDocx.FullName);
+
+ var before = sourceCopiedToDestDocx.Name.Replace(".docx", "");
+ var docxComparedFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, before + "-COMPARE" + ".docx"));
+ var docxCompared2Fi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, before + "-COMPARE2" + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(sourceCopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(sourceCopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxComparedFi.FullName);
+ ValidateDocument(comparedWml);
+
+ WmlDocument comparedWml2 = WmlComparer.Compare(comparedWml, source1Wml, settings);
+ comparedWml2.SaveAs(docxCompared2Fi.FullName);
+ ValidateDocument(comparedWml2);
+ }
+
+ [Theory]
+ [InlineData("WC040-Case-Before.docx", "WC040-Case-After.docx", 2)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+ //[InlineData("", "", 0)]
+
+ public void WC005_Compare_CaseInsensitive(string name1, string name2, int revisionCount)
+ {
+ FileInfo source1Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2Docx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source1Docx.Name));
+ var source2CopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source2Docx.Name));
+ if (!source1CopiedToDestDocx.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocx.FullName);
+ if (!source2CopiedToDestDocx.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocx.FullName);
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo source1DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name1));
+ FileInfo source2DocxForWord = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name2));
+
+ var source1CopiedToDestDocxForWord = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source1Docx.Name.Replace(".docx", "-For-Word.docx")));
+ var source2CopiedToDestDocxForWord = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, source2Docx.Name.Replace(".docx", "-For-Word.docx")));
+ if (!source1CopiedToDestDocxForWord.Exists)
+ File.Copy(source1Docx.FullName, source1CopiedToDestDocxForWord.FullName);
+ if (!source2CopiedToDestDocxForWord.Exists)
+ File.Copy(source2Docx.FullName, source2CopiedToDestDocxForWord.FullName);
+
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ var path = new DirectoryInfo(@"C:\Users\Eric\Documents\WindowsPowerShellModules\Open-Xml-PowerTools\TestFiles");
+ WordRunner.RunWord(wordExe, source2CopiedToDestDocxForWord);
+ WordRunner.RunWord(wordExe, source1CopiedToDestDocxForWord);
+ }
+
+ /************************************************************************************************************************/
+
+ var before = source1CopiedToDestDocx.Name.Replace(".docx", "");
+ var after = source2CopiedToDestDocx.Name.Replace(".docx", "");
+ var docxWithRevisionsFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, before + "-COMPARE-" + after + ".docx"));
+
+ WmlDocument source1Wml = new WmlDocument(source1CopiedToDestDocx.FullName);
+ WmlDocument source2Wml = new WmlDocument(source2CopiedToDestDocx.FullName);
+ WmlComparerSettings settings = new WmlComparerSettings();
+ settings.CaseInsensitive = true;
+ settings.CultureInfo = System.Globalization.CultureInfo.CurrentCulture;
+ WmlDocument comparedWml = WmlComparer.Compare(source1Wml, source2Wml, settings);
+ comparedWml.SaveAs(docxWithRevisionsFi.FullName);
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(comparedWml.DocumentByteArray, 0, comparedWml.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() > 0)
+ {
+
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ Assert.Equal("", sbs);
+ }
+ }
+ }
+
+ /************************************************************************************************************************/
+
+ if (s_OpenWord)
+ {
+ FileInfo wordExe = new FileInfo(@"C:\Program Files (x86)\Microsoft Office\root\Office16\WINWORD.EXE");
+ WordRunner.RunWord(wordExe, docxWithRevisionsFi);
+ }
+
+ /************************************************************************************************************************/
+
+ WmlDocument revisionWml = new WmlDocument(docxWithRevisionsFi.FullName);
+ var revisions = WmlComparer.GetRevisions(revisionWml, settings);
+ Assert.Equal(revisionCount, revisions.Count());
+ }
+#endif
+
+ private static void ValidateDocument(WmlDocument wmlToValidate)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlToValidate.DocumentByteArray, 0, wmlToValidate.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator();
+ var errors = validator.Validate(wDoc).Where(e => !ExpectedErrors.Contains(e.Description));
+ if (errors.Count() != 0)
+ {
+ var ind = " ";
+ var sb = new StringBuilder();
+ foreach (var err in errors)
+ {
+ sb.Append("Error" + Environment.NewLine);
+ sb.Append(ind + "ErrorType: " + err.ErrorType.ToString() + Environment.NewLine);
+ sb.Append(ind + "Description: " + err.Description + Environment.NewLine);
+ sb.Append(ind + "Part: " + err.Part.Uri.ToString() + Environment.NewLine);
+ sb.Append(ind + "XPath: " + err.Path.XPath + Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ Assert.Equal("", sbs);
+ }
+ }
+ }
+ }
+
+ public static string[] ExpectedErrors = new string[] {
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastRow' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:firstColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:lastColumn' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noHBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:noVBand' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:allStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:customStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:latentStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:stylesInUse' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:headingStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:numberingStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:tableStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnRuns' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnParagraphs' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnNumbering' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:directFormattingOnTables' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:clearFormatting' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:top3HeadingStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:visibleStyles' attribute is not declared.",
+ "The 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:alternateStyleNames' attribute is not declared.",
+ "The attribute 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:val' has invalid value '0'. The MinInclusive constraint failed. The value must be greater than or equal to 1.",
+ "The attribute 'http://schemas.openxmlformats.org/wordprocessingml/2006/main:val' has invalid value '0'. The MinInclusive constraint failed. The value must be greater than or equal to 2.",
+ };
+
+ }
+#if false
+ public class WordRunner
+ {
+ public static void RunWord(FileInfo executablePath, FileInfo docxPath)
+ {
+ if (executablePath.Exists)
+ {
+ using (Process proc = new Process())
+ {
+ proc.StartInfo.FileName = executablePath.FullName;
+ proc.StartInfo.Arguments = docxPath.FullName;
+ proc.StartInfo.WorkingDirectory = docxPath.DirectoryName;
+ proc.StartInfo.UseShellExecute = false;
+ proc.StartInfo.RedirectStandardOutput = true;
+ proc.StartInfo.RedirectStandardError = true;
+ proc.Start();
+ }
+ }
+ else
+ {
+ throw new ArgumentException("Invalid executable path.", "executablePath");
+ }
+ }
+ }
+#endif
+}
+
+#endif
diff --git a/OpenXmlPowerTools.Tests/WmlContentAtomListTests.cs b/OpenXmlPowerTools.Tests/WmlContentAtomListTests.cs
new file mode 100644
index 0000000..2468486
--- /dev/null
+++ b/OpenXmlPowerTools.Tests/WmlContentAtomListTests.cs
@@ -0,0 +1,157 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+#define COPY_FILES_FOR_DEBUGGING
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using Xunit;
+
+#if !ELIDE_XUNIT_TESTS
+
+namespace OxPt
+{
+ public class CaTests
+ {
+ [Theory]
+ [InlineData("CA/CA001-Plain.docx", 60)]
+ [InlineData("CA/CA002-Bookmark.docx", 7)]
+ [InlineData("CA/CA003-Numbered-List.docx", 8)]
+ [InlineData("CA/CA004-TwoParas.docx", 88)]
+ [InlineData("CA/CA005-Table.docx", 27)]
+ [InlineData("CA/CA006-ContentControl.docx", 60)]
+ [InlineData("CA/CA007-DayLong.docx", 10)]
+ [InlineData("CA/CA008-Footnote-Reference.docx", 23)]
+ [InlineData("CA/CA010-Delete-Run.docx", 16)]
+ [InlineData("CA/CA011-Insert-Run.docx", 16)]
+ [InlineData("CA/CA012-fldSimple.docx", 10)]
+ [InlineData("CA/CA013-Lots-of-Stuff.docx", 168)]
+ [InlineData("CA/CA014-Complex-Table.docx", 193)]
+ [InlineData("WC/WC024-Table-Before.docx", 24)]
+ [InlineData("WC/WC024-Table-After2.docx", 18)]
+ //[InlineData("", 0)]
+ //[InlineData("", 0)]
+ //[InlineData("", 0)]
+ //[InlineData("", 0)]
+
+ public void CA001_ContentAtoms(string name, int contentAtomCount)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+ var thisGuid = Guid.NewGuid().ToString().Replace("-", "");
+ var sourceCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", string.Format("-{0}-1-Source.docx", thisGuid))));
+ if (!sourceCopiedToDestDocx.Exists)
+ File.Copy(sourceDocx.FullName, sourceCopiedToDestDocx.FullName);
+
+ var coalescedDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", string.Format("-{0}-2-Coalesced.docx", thisGuid))));
+ if (!coalescedDocx.Exists)
+ File.Copy(sourceDocx.FullName, coalescedDocx.FullName);
+
+ var contentAtomDataFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", string.Format("-{0}-3-ContentAtomData.txt", thisGuid))));
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(coalescedDocx.FullName, true))
+ {
+ var contentParent = wDoc.MainDocumentPart.GetXDocument().Root.Element(W.body);
+ var settings = new WmlComparerSettings();
+ ComparisonUnitAtom[] contentAtomList = WmlComparer.CreateComparisonUnitAtomList(wDoc.MainDocumentPart, contentParent, settings);
+ StringBuilder sb = new StringBuilder();
+ var part = wDoc.MainDocumentPart;
+
+ sb.AppendFormat("Part: {0}", part.Uri.ToString());
+ sb.Append(Environment.NewLine);
+ sb.Append(ComparisonUnit.ComparisonUnitListToString(contentAtomList.ToArray()) + Environment.NewLine);
+ sb.Append(Environment.NewLine);
+
+ XDocument newMainXDoc = WmlComparer.Coalesce(contentAtomList);
+ var partXDoc = wDoc.MainDocumentPart.GetXDocument();
+ partXDoc.Root.ReplaceWith(newMainXDoc.Root);
+ wDoc.MainDocumentPart.PutXDocument();
+
+ File.WriteAllText(contentAtomDataFi.FullName, sb.ToString());
+
+ Assert.Equal(contentAtomCount, contentAtomList.Count());
+ }
+ }
+
+ [Theory]
+ [InlineData("HC009-Test-04.docx")]
+ public void CA002_Annotations(string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+
+#if COPY_FILES_FOR_DEBUGGING
+ var sourceCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-1-Source.docx")));
+ if (!sourceCopiedToDestDocx.Exists)
+ File.Copy(sourceDocx.FullName, sourceCopiedToDestDocx.FullName);
+
+ var annotatedDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", "-2-Annotated.docx")));
+ if (!annotatedDocx.Exists)
+ File.Copy(sourceDocx.FullName, annotatedDocx.FullName);
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(annotatedDocx.FullName, true))
+ {
+ var contentParent = wDoc.MainDocumentPart.GetXDocument().Root.Element(W.body);
+ var settings = new WmlComparerSettings();
+ WmlComparer.CreateComparisonUnitAtomList(wDoc.MainDocumentPart, contentParent, settings);
+ }
+#endif
+ }
+
+ [Theory]
+ [InlineData("CA/CA009-altChunk.docx")]
+ //[InlineData("")]
+ //[InlineData("")]
+ //[InlineData("")]
+
+ public void CA003_ContentAtoms_Throws(string name)
+ {
+ FileInfo sourceDocx = new FileInfo(Path.Combine(TestUtil.SourceDir.FullName, name));
+ var thisGuid = Guid.NewGuid().ToString().Replace("-", "");
+ var sourceCopiedToDestDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", string.Format("-{0}-1-Source.docx", thisGuid))));
+ if (!sourceCopiedToDestDocx.Exists)
+ File.Copy(sourceDocx.FullName, sourceCopiedToDestDocx.FullName);
+
+ var coalescedDocx = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", string.Format("-{0}-2-Coalesced.docx", thisGuid))));
+ if (!coalescedDocx.Exists)
+ File.Copy(sourceDocx.FullName, coalescedDocx.FullName);
+
+ var contentAtomDataFi = new FileInfo(Path.Combine(TestUtil.TempDir.FullName, sourceDocx.Name.Replace(".docx", string.Format("-{0}-3-ContentAtomData.txt", thisGuid))));
+
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(coalescedDocx.FullName, true))
+ {
+ Assert.Throws<NotSupportedException>(() =>
+ {
+ var contentParent = wDoc.MainDocumentPart.GetXDocument().Root.Element(W.body);
+ var settings = new WmlComparerSettings();
+ WmlComparer.CreateComparisonUnitAtomList(wDoc.MainDocumentPart, contentParent, settings);
+ });
+ }
+ }
+ }
+}
+
+#endif
diff --git a/OpenXmlPowerTools.sln b/OpenXmlPowerTools.sln
new file mode 100644
index 0000000..5b082f8
--- /dev/null
+++ b/OpenXmlPowerTools.sln
@@ -0,0 +1,272 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2036
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenXmlPowerTools", "OpenXmlPowerTools\OpenXmlPowerTools.csproj", "{6F957FF3-AFCC-4D69-8FBC-71AE21BC45C9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChartUpdater01", "OpenXmlPowerToolsExamples\ChartUpdater01\ChartUpdater01.csproj", "{1B03D24F-FB08-42E1-BB25-2D1BB907991B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentBuilder01", "OpenXmlPowerToolsExamples\DocumentBuilder01\DocumentBuilder01.csproj", "{A8A6FCF0-CA4F-4637-8E66-D86603B92204}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentBuilder02", "OpenXmlPowerToolsExamples\DocumentBuilder02\DocumentBuilder02.csproj", "{BF8153AA-C390-4D00-98F2-083DEFEC7094}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentBuilder03", "OpenXmlPowerToolsExamples\DocumentBuilder03\DocumentBuilder03.csproj", "{BE635672-D3FD-42C5-96E3-EDE50E05CE29}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentBuilder04", "OpenXmlPowerToolsExamples\DocumentBuilder04\DocumentBuilder04.csproj", "{CE0ECABB-03AD-42CE-A5BD-D0CC4DCE35AB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FieldRetriever01", "OpenXmlPowerToolsExamples\FieldRetriever01\FieldRetriever01.csproj", "{E1FD7B15-FA09-4F7F-A30E-9FBD9F765D02}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FormattingAssembler01", "OpenXmlPowerToolsExamples\FormattingAssembler01\FormattingAssembler01.csproj", "{6EAF15B6-98BE-428B-A018-A5266521551E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Formulas01", "OpenXmlPowerToolsExamples\Formulas01\Formulas01.csproj", "{618B95DB-3A70-4DC8-BD87-00F636F72453}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HtmlConverter01", "OpenXmlPowerToolsExamples\HtmlConverter01\HtmlConverter01.csproj", "{BC9E7408-508D-482D-8602-96E76ACBB5EA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ListItemRetriever01", "OpenXmlPowerToolsExamples\ListItemRetriever01\ListItemRetriever01.csproj", "{04935FA6-E48B-496F-8D6A-41A59232303D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MarkupSimplifierApp", "OpenXmlPowerToolsExamples\MarkupSimplifierApp\MarkupSimplifierApp.csproj", "{0CCE83BA-8B8B-4B98-8846-B62A61FDF170}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MetricsGetter01", "OpenXmlPowerToolsExamples\MetricsGetter01\MetricsGetter01.csproj", "{ED37372C-DFBF-4720-BD4B-CE1415961038}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenXmlRegex01", "OpenXmlPowerToolsExamples\OpenXmlRegex01\OpenXmlRegex01.csproj", "{4330D46F-6703-4BA2-844D-177F722C0820}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PivotTables01", "OpenXmlPowerToolsExamples\PivotTables01\PivotTables01.csproj", "{6785A554-BBBB-480C-8E4D-9FE90DD91A46}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationBuilder01", "OpenXmlPowerToolsExamples\PresentationBuilder01\PresentationBuilder01.csproj", "{A4128935-B707-4D56-B924-27910EC92664}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationBuilder02", "OpenXmlPowerToolsExamples\PresentationBuilder02\PresentationBuilder02.csproj", "{495060B4-BD80-4B18-AC44-4680F0D6D5DB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReferenceAdder01", "OpenXmlPowerToolsExamples\ReferenceAdder01\ReferenceAdder01.csproj", "{211F05D3-F8EE-497F-9DE9-AFFA0A72140D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RevisionAccepter01", "OpenXmlPowerToolsExamples\RevisionAccepter01\RevisionAccepter01.csproj", "{84823BA5-B860-4CAF-885E-981D7F121830}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextReplacer01", "OpenXmlPowerToolsExamples\TextReplacer01\TextReplacer01.csproj", "{5CA29B44-C4A5-4310-9429-553F0BC9FC1D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextReplacer02", "OpenXmlPowerToolsExamples\TextReplacer02\TextReplacer02.csproj", "{153E5218-E9C0-4ED8-9073-0802204C8B90}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentAssembler01", "OpenXmlPowerToolsExamples\DocumentAssembler01\DocumentAssembler01.csproj", "{EFE77658-EDD7-4597-8016-CBE4A18951B0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentAssembler", "OpenXmlPowerToolsExamples\DocumentAssembler\DocumentAssembler.csproj", "{2D081A36-48F9-4A09-8247-420682545492}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpreadsheetWriter01", "OpenXmlPowerToolsExamples\SpreadsheetWriter01\SpreadsheetWriter01.csproj", "{0911F996-DF86-4FB1-A1CE-0539599EC0E0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpreadsheetWriter02", "OpenXmlPowerToolsExamples\SpreadsheetWriter02\SpreadsheetWriter02.csproj", "{DF1D84AF-27B8-4F87-A673-CCFADC3AAF02}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenXmlPowerTools.Tests", "OpenXmlPowerTools.Tests\OpenXmlPowerTools.Tests.csproj", "{B1328B70-33B8-40E0-A729-38C34D659B5C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentAssembler02", "OpenXmlPowerToolsExamples\DocumentAssembler02\DocumentAssembler02.csproj", "{D6DFAC7C-82F4-4318-A9F0-6450D2945D96}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HtmlToWmlConverter01", "OpenXmlPowerToolsExamples\HtmlToWmlConverter01\HtmlToWmlConverter01.csproj", "{9E194D13-F5A0-4430-8F87-D74326457385}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentAssembler03", "OpenXmlPowerToolsExamples\DocumentAssembler03\DocumentAssembler03.csproj", "{214F0E80-D2E3-4E19-A3FC-2BE15B3906B0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HtmlToWmlConverter02", "OpenXmlPowerToolsExamples\HtmlToWmlConverter02\HtmlToWmlConverter02.csproj", "{1E75CB7D-2A4A-4B03-80A9-7B7D59B18BB4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WmlToHtmlConverter01", "OpenXmlPowerToolsExamples\WmlToHtmlConverter01\WmlToHtmlConverter01.csproj", "{396D9209-0D9A-4E61-9471-04C71F6CA6B9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WmlToHtmlConverter02", "OpenXmlPowerToolsExamples\WmlToHtmlConverter02\WmlToHtmlConverter02.csproj", "{D4078011-2611-46A7-8A30-55E4AB8FA786}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmlDataRetriever01", "OpenXmlPowerToolsExamples\SmlDataRetriever01\SmlDataRetriever01.csproj", "{DCE8EC51-1E58-49A0-82CF-5BE269FA0A9D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WmlComparer01", "OpenXmlPowerToolsExamples\WmlComparer01\WmlComparer01.csproj", "{C9CAA69C-575A-442E-9D8A-69C53B39D72C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WmlComparer02", "OpenXmlPowerToolsExamples\WmlComparer02\WmlComparer02.csproj", "{3D1C46E1-7A9E-4A4B-8D95-68613603DEDF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{A83D6B58-6D38-46AF-8C20-5CFC170A1063}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9AFB8C96-1E6E-483E-9882-75D2483E7076}"
+ ProjectSection(SolutionItems) = preProject
+ LICENSE.txt = LICENSE.txt
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6F957FF3-AFCC-4D69-8FBC-71AE21BC45C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F957FF3-AFCC-4D69-8FBC-71AE21BC45C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6F957FF3-AFCC-4D69-8FBC-71AE21BC45C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6F957FF3-AFCC-4D69-8FBC-71AE21BC45C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1B03D24F-FB08-42E1-BB25-2D1BB907991B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1B03D24F-FB08-42E1-BB25-2D1BB907991B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1B03D24F-FB08-42E1-BB25-2D1BB907991B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1B03D24F-FB08-42E1-BB25-2D1BB907991B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A8A6FCF0-CA4F-4637-8E66-D86603B92204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8A6FCF0-CA4F-4637-8E66-D86603B92204}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8A6FCF0-CA4F-4637-8E66-D86603B92204}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8A6FCF0-CA4F-4637-8E66-D86603B92204}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BF8153AA-C390-4D00-98F2-083DEFEC7094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BF8153AA-C390-4D00-98F2-083DEFEC7094}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BF8153AA-C390-4D00-98F2-083DEFEC7094}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BF8153AA-C390-4D00-98F2-083DEFEC7094}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BE635672-D3FD-42C5-96E3-EDE50E05CE29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BE635672-D3FD-42C5-96E3-EDE50E05CE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BE635672-D3FD-42C5-96E3-EDE50E05CE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BE635672-D3FD-42C5-96E3-EDE50E05CE29}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CE0ECABB-03AD-42CE-A5BD-D0CC4DCE35AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CE0ECABB-03AD-42CE-A5BD-D0CC4DCE35AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CE0ECABB-03AD-42CE-A5BD-D0CC4DCE35AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CE0ECABB-03AD-42CE-A5BD-D0CC4DCE35AB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E1FD7B15-FA09-4F7F-A30E-9FBD9F765D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E1FD7B15-FA09-4F7F-A30E-9FBD9F765D02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E1FD7B15-FA09-4F7F-A30E-9FBD9F765D02}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E1FD7B15-FA09-4F7F-A30E-9FBD9F765D02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6EAF15B6-98BE-428B-A018-A5266521551E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6EAF15B6-98BE-428B-A018-A5266521551E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6EAF15B6-98BE-428B-A018-A5266521551E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6EAF15B6-98BE-428B-A018-A5266521551E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {618B95DB-3A70-4DC8-BD87-00F636F72453}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {618B95DB-3A70-4DC8-BD87-00F636F72453}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {618B95DB-3A70-4DC8-BD87-00F636F72453}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {618B95DB-3A70-4DC8-BD87-00F636F72453}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BC9E7408-508D-482D-8602-96E76ACBB5EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BC9E7408-508D-482D-8602-96E76ACBB5EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BC9E7408-508D-482D-8602-96E76ACBB5EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BC9E7408-508D-482D-8602-96E76ACBB5EA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {04935FA6-E48B-496F-8D6A-41A59232303D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {04935FA6-E48B-496F-8D6A-41A59232303D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {04935FA6-E48B-496F-8D6A-41A59232303D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {04935FA6-E48B-496F-8D6A-41A59232303D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0CCE83BA-8B8B-4B98-8846-B62A61FDF170}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0CCE83BA-8B8B-4B98-8846-B62A61FDF170}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0CCE83BA-8B8B-4B98-8846-B62A61FDF170}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0CCE83BA-8B8B-4B98-8846-B62A61FDF170}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED37372C-DFBF-4720-BD4B-CE1415961038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ED37372C-DFBF-4720-BD4B-CE1415961038}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED37372C-DFBF-4720-BD4B-CE1415961038}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ED37372C-DFBF-4720-BD4B-CE1415961038}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4330D46F-6703-4BA2-844D-177F722C0820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4330D46F-6703-4BA2-844D-177F722C0820}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4330D46F-6703-4BA2-844D-177F722C0820}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4330D46F-6703-4BA2-844D-177F722C0820}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6785A554-BBBB-480C-8E4D-9FE90DD91A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6785A554-BBBB-480C-8E4D-9FE90DD91A46}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6785A554-BBBB-480C-8E4D-9FE90DD91A46}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6785A554-BBBB-480C-8E4D-9FE90DD91A46}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4128935-B707-4D56-B924-27910EC92664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4128935-B707-4D56-B924-27910EC92664}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4128935-B707-4D56-B924-27910EC92664}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4128935-B707-4D56-B924-27910EC92664}.Release|Any CPU.Build.0 = Release|Any CPU
+ {495060B4-BD80-4B18-AC44-4680F0D6D5DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {495060B4-BD80-4B18-AC44-4680F0D6D5DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {495060B4-BD80-4B18-AC44-4680F0D6D5DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {495060B4-BD80-4B18-AC44-4680F0D6D5DB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {211F05D3-F8EE-497F-9DE9-AFFA0A72140D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {211F05D3-F8EE-497F-9DE9-AFFA0A72140D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {211F05D3-F8EE-497F-9DE9-AFFA0A72140D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {211F05D3-F8EE-497F-9DE9-AFFA0A72140D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84823BA5-B860-4CAF-885E-981D7F121830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {84823BA5-B860-4CAF-885E-981D7F121830}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84823BA5-B860-4CAF-885E-981D7F121830}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {84823BA5-B860-4CAF-885E-981D7F121830}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5CA29B44-C4A5-4310-9429-553F0BC9FC1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5CA29B44-C4A5-4310-9429-553F0BC9FC1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5CA29B44-C4A5-4310-9429-553F0BC9FC1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5CA29B44-C4A5-4310-9429-553F0BC9FC1D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {153E5218-E9C0-4ED8-9073-0802204C8B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {153E5218-E9C0-4ED8-9073-0802204C8B90}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {153E5218-E9C0-4ED8-9073-0802204C8B90}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {153E5218-E9C0-4ED8-9073-0802204C8B90}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EFE77658-EDD7-4597-8016-CBE4A18951B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EFE77658-EDD7-4597-8016-CBE4A18951B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EFE77658-EDD7-4597-8016-CBE4A18951B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EFE77658-EDD7-4597-8016-CBE4A18951B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D081A36-48F9-4A09-8247-420682545492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D081A36-48F9-4A09-8247-420682545492}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D081A36-48F9-4A09-8247-420682545492}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D081A36-48F9-4A09-8247-420682545492}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0911F996-DF86-4FB1-A1CE-0539599EC0E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0911F996-DF86-4FB1-A1CE-0539599EC0E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0911F996-DF86-4FB1-A1CE-0539599EC0E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0911F996-DF86-4FB1-A1CE-0539599EC0E0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF1D84AF-27B8-4F87-A673-CCFADC3AAF02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF1D84AF-27B8-4F87-A673-CCFADC3AAF02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF1D84AF-27B8-4F87-A673-CCFADC3AAF02}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF1D84AF-27B8-4F87-A673-CCFADC3AAF02}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1328B70-33B8-40E0-A729-38C34D659B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1328B70-33B8-40E0-A729-38C34D659B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1328B70-33B8-40E0-A729-38C34D659B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1328B70-33B8-40E0-A729-38C34D659B5C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6DFAC7C-82F4-4318-A9F0-6450D2945D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6DFAC7C-82F4-4318-A9F0-6450D2945D96}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6DFAC7C-82F4-4318-A9F0-6450D2945D96}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6DFAC7C-82F4-4318-A9F0-6450D2945D96}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E194D13-F5A0-4430-8F87-D74326457385}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E194D13-F5A0-4430-8F87-D74326457385}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E194D13-F5A0-4430-8F87-D74326457385}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E194D13-F5A0-4430-8F87-D74326457385}.Release|Any CPU.Build.0 = Release|Any CPU
+ {214F0E80-D2E3-4E19-A3FC-2BE15B3906B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {214F0E80-D2E3-4E19-A3FC-2BE15B3906B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {214F0E80-D2E3-4E19-A3FC-2BE15B3906B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {214F0E80-D2E3-4E19-A3FC-2BE15B3906B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E75CB7D-2A4A-4B03-80A9-7B7D59B18BB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E75CB7D-2A4A-4B03-80A9-7B7D59B18BB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E75CB7D-2A4A-4B03-80A9-7B7D59B18BB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E75CB7D-2A4A-4B03-80A9-7B7D59B18BB4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {396D9209-0D9A-4E61-9471-04C71F6CA6B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {396D9209-0D9A-4E61-9471-04C71F6CA6B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {396D9209-0D9A-4E61-9471-04C71F6CA6B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {396D9209-0D9A-4E61-9471-04C71F6CA6B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4078011-2611-46A7-8A30-55E4AB8FA786}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4078011-2611-46A7-8A30-55E4AB8FA786}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4078011-2611-46A7-8A30-55E4AB8FA786}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4078011-2611-46A7-8A30-55E4AB8FA786}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DCE8EC51-1E58-49A0-82CF-5BE269FA0A9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DCE8EC51-1E58-49A0-82CF-5BE269FA0A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DCE8EC51-1E58-49A0-82CF-5BE269FA0A9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DCE8EC51-1E58-49A0-82CF-5BE269FA0A9D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9CAA69C-575A-442E-9D8A-69C53B39D72C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C9CAA69C-575A-442E-9D8A-69C53B39D72C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9CAA69C-575A-442E-9D8A-69C53B39D72C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C9CAA69C-575A-442E-9D8A-69C53B39D72C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D1C46E1-7A9E-4A4B-8D95-68613603DEDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D1C46E1-7A9E-4A4B-8D95-68613603DEDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D1C46E1-7A9E-4A4B-8D95-68613603DEDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D1C46E1-7A9E-4A4B-8D95-68613603DEDF}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1B03D24F-FB08-42E1-BB25-2D1BB907991B} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {A8A6FCF0-CA4F-4637-8E66-D86603B92204} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {BF8153AA-C390-4D00-98F2-083DEFEC7094} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {BE635672-D3FD-42C5-96E3-EDE50E05CE29} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {CE0ECABB-03AD-42CE-A5BD-D0CC4DCE35AB} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {E1FD7B15-FA09-4F7F-A30E-9FBD9F765D02} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {6EAF15B6-98BE-428B-A018-A5266521551E} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {618B95DB-3A70-4DC8-BD87-00F636F72453} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {BC9E7408-508D-482D-8602-96E76ACBB5EA} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {04935FA6-E48B-496F-8D6A-41A59232303D} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {0CCE83BA-8B8B-4B98-8846-B62A61FDF170} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {ED37372C-DFBF-4720-BD4B-CE1415961038} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {4330D46F-6703-4BA2-844D-177F722C0820} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {6785A554-BBBB-480C-8E4D-9FE90DD91A46} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {A4128935-B707-4D56-B924-27910EC92664} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {495060B4-BD80-4B18-AC44-4680F0D6D5DB} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {211F05D3-F8EE-497F-9DE9-AFFA0A72140D} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {84823BA5-B860-4CAF-885E-981D7F121830} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {5CA29B44-C4A5-4310-9429-553F0BC9FC1D} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {153E5218-E9C0-4ED8-9073-0802204C8B90} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {EFE77658-EDD7-4597-8016-CBE4A18951B0} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {2D081A36-48F9-4A09-8247-420682545492} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {0911F996-DF86-4FB1-A1CE-0539599EC0E0} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {DF1D84AF-27B8-4F87-A673-CCFADC3AAF02} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {D6DFAC7C-82F4-4318-A9F0-6450D2945D96} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {9E194D13-F5A0-4430-8F87-D74326457385} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {214F0E80-D2E3-4E19-A3FC-2BE15B3906B0} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {1E75CB7D-2A4A-4B03-80A9-7B7D59B18BB4} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {396D9209-0D9A-4E61-9471-04C71F6CA6B9} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {D4078011-2611-46A7-8A30-55E4AB8FA786} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {DCE8EC51-1E58-49A0-82CF-5BE269FA0A9D} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {C9CAA69C-575A-442E-9D8A-69C53B39D72C} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ {3D1C46E1-7A9E-4A4B-8D95-68613603DEDF} = {A83D6B58-6D38-46AF-8C20-5CFC170A1063}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {E623EFF5-2CA4-4FA0-B3AB-53F921DA212E}
+ EndGlobalSection
+EndGlobal
diff --git a/OpenXmlPowerTools/ChartUpdater.cs b/OpenXmlPowerTools/ChartUpdater.cs
new file mode 100644
index 0000000..5db2181
--- /dev/null
+++ b/OpenXmlPowerTools/ChartUpdater.cs
@@ -0,0 +1,617 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ public enum ChartDataType
+ {
+ Number,
+ String,
+ DateTime,
+ }
+
+ // Format Codes
+ // 0 - general
+ // 1 - 0
+ // 2 - 0.00
+ // 3 - #,##0
+ // 4 - #,##0.00
+ // 9 - 0%
+ // 10 - 0.00%
+ // 11 - 0.00E+00
+ // 12 - # ?/?
+ // 13 - # ??/??
+ // 14 - mm-dd-yy
+ // 15 - d-mmm-yy
+ // 16 - d-mmm
+ // 17 - mmm-yy
+ // 18 - h:mm AM/PM
+ // 19 - h:mm:ss AM/PM
+ // 20 - h:mm
+ // 21 - h:mm:ss
+ // 22 - m/d/yy h:mm
+ // 37 - #,##0 ;(#,##0)
+ // 38 - #,##0 ;[Red](#,##0)
+ // 39 - #,##0.00;(#,##0.00)
+ // 40 - #,##0.00;[Red](#,##0.00)
+ // 45 - mm:ss
+ // 46 - [h]:mm:ss
+ // 47 - mmss.0
+ // 48 - ##0.0E+0
+ // 49 - @
+
+ public class ChartData
+ {
+ public string[] SeriesNames;
+
+ public ChartDataType CategoryDataType;
+ public int CategoryFormatCode;
+ public string[] CategoryNames;
+
+ public double[][] Values;
+ }
+
+ public class ChartUpdater
+ {
+ public static bool UpdateChart(WordprocessingDocument wDoc, string contentControlTag, ChartData chartData)
+ {
+ var mainDocumentPart = wDoc.MainDocumentPart;
+ var mdXDoc = mainDocumentPart.GetXDocument();
+ var cc = mdXDoc.Descendants(W.sdt)
+ .FirstOrDefault(sdt => (string)sdt.Elements(W.sdtPr).Elements(W.tag).Attributes(W.val).FirstOrDefault() == contentControlTag);
+ if (cc != null)
+ {
+ var chartRid = (string)cc.Descendants(C.chart).Attributes(R.id).FirstOrDefault();
+ if (chartRid != null)
+ {
+ ChartPart chartPart = (ChartPart)mainDocumentPart.GetPartById(chartRid);
+ UpdateChart(chartPart, chartData);
+ var newContent = cc.Elements(W.sdtContent).Elements().Select(e => new XElement(e));
+ cc.ReplaceWith(newContent);
+ mainDocumentPart.PutXDocument();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static void UpdateChart(ChartPart chartPart, ChartData chartData)
+ {
+ if (chartData.Values.Length != chartData.SeriesNames.Length)
+ throw new ArgumentException("Invalid chart data");
+ foreach (var ser in chartData.Values)
+ {
+ if (ser.Length != chartData.CategoryNames.Length)
+ throw new ArgumentException("Invalid chart data");
+ }
+
+ UpdateSeries(chartPart, chartData);
+ }
+
+ private static Dictionary<int, string> FormatCodes = new Dictionary<int, string>()
+ {
+ { 0, "general" },
+ { 1, "0" },
+ { 2, "0.00" },
+ { 3, "#,##0" },
+ { 4, "#,##0.00" },
+ { 9, "0%" },
+ { 10, "0.00%" },
+ { 11, "0.00E+00" },
+ { 12, "# ?/?" },
+ { 13, "# ??/??" },
+ { 14, "mm-dd-yy" },
+ { 15, "d-mmm-yy" },
+ { 16, "d-mmm" },
+ { 17, "mmm-yy" },
+ { 18, "h:mm AM/PM" },
+ { 19, "h:mm:ss AM/PM" },
+ { 20, "h:mm" },
+ { 21, "h:mm:ss" },
+ { 22, "m/d/yy h:mm" },
+ { 37, "#,##0 ;(#,##0)" },
+ { 38, "#,##0 ;[Red](#,##0)" },
+ { 39, "#,##0.00;(#,##0.00)" },
+ { 40, "#,##0.00;[Red](#,##0.00)" },
+ { 45, "mm:ss" },
+ { 46, "[h]:mm:ss" },
+ { 47, "mmss.0" },
+ { 48, "##0.0E+0" },
+ { 49, "@" },
+ };
+
+ private static void UpdateSeries(ChartPart chartPart, ChartData chartData)
+ {
+ UpdateEmbeddedWorkbook(chartPart, chartData);
+
+ XDocument cpXDoc = chartPart.GetXDocument();
+ XElement root = cpXDoc.Root;
+ var firstSeries = root.Descendants(C.ser).FirstOrDefault();
+ var numRef = firstSeries.Elements(C.val).Elements(C.numRef).FirstOrDefault();
+ string sheetName = null;
+ var f = (string)firstSeries.Descendants(C.f).FirstOrDefault();
+ if (f != null)
+ sheetName = f.Split('!')[0];
+
+ // remove all but first series
+ XName chartType = firstSeries.Parent.Name;
+ firstSeries.Parent.Elements(C.ser).Skip(1).Remove();
+
+ var newSetOfSeries = chartData.SeriesNames
+ .Select((string sn, int si) =>
+ {
+ XElement cat = null;
+
+ var oldCat = firstSeries.Elements(C.cat).FirstOrDefault();
+ if (oldCat == null)
+ throw new OpenXmlPowerToolsException("Invalid chart markup");
+
+ var catHasFormula = oldCat.Descendants(C.f).Any();
+ if (catHasFormula)
+ {
+ XElement newFormula = null;
+ if (sheetName != null)
+ newFormula = new XElement(C.f, string.Format("{0}!$A$2:$A${1}", sheetName, chartData.CategoryNames.Length + 1));
+ if (chartData.CategoryDataType == ChartDataType.String)
+ {
+ cat = new XElement(C.cat,
+ new XElement(C.strRef,
+ newFormula,
+ new XElement(C.strCache,
+ new XElement(C.ptCount, new XAttribute("val", chartData.CategoryNames.Length)),
+ chartData.CategoryNames.Select((string cn, int ci) =>
+ {
+ var newPt = new XElement(C.pt,
+ new XAttribute("idx", ci),
+ new XElement(C.v, chartData.CategoryNames[ci]));
+ return newPt;
+ }))));
+ }
+ else
+ {
+ cat = new XElement(C.cat,
+ new XElement(C.numRef,
+ newFormula,
+ new XElement(C.numCache,
+ new XElement(C.formatCode, FormatCodes[chartData.CategoryFormatCode]),
+ new XElement(C.ptCount, new XAttribute("val", chartData.CategoryNames.Length)),
+ chartData.CategoryNames.Select((string cn, int ci) =>
+ {
+ var newPt = new XElement(C.pt,
+ new XAttribute("idx", ci),
+ new XElement(C.v, chartData.CategoryNames[ci]));
+ return newPt;
+ }))));
+ }
+ }
+ else
+ {
+ if (chartData.CategoryDataType == ChartDataType.String)
+ {
+ cat = new XElement(C.cat,
+ new XElement(C.strLit,
+ new XElement(C.ptCount, new XAttribute("val", chartData.CategoryNames.Length)),
+ chartData.CategoryNames.Select((string cn, int ci) =>
+ {
+ var newPt = new XElement(C.pt,
+ new XAttribute("idx", ci),
+ new XElement(C.v, chartData.CategoryNames[ci]));
+ return newPt;
+ })));
+ }
+ else
+ {
+ cat = new XElement(C.cat,
+ new XElement(C.numLit,
+ new XElement(C.ptCount, new XAttribute("val", chartData.CategoryNames.Length)),
+ chartData.CategoryNames.Select((string cn, int ci) =>
+ {
+ var newPt = new XElement(C.pt,
+ new XAttribute("idx", ci),
+ new XElement(C.v, chartData.CategoryNames[ci]));
+ return newPt;
+ })));
+ }
+ }
+
+ XElement newCval = null;
+
+ if (sheetName == null)
+ {
+ newCval = new XElement(C.val,
+ new XElement(C.numLit,
+ new XElement(C.ptCount, new XAttribute("val", chartData.CategoryNames.Length)),
+ chartData.CategoryNames.Select((string cn, int ci) =>
+ {
+ var newPt = new XElement(C.pt,
+ new XAttribute("idx", ci),
+ new XElement(C.v, chartData.Values[si][ci]));
+ return newPt;
+ })));
+ }
+ else
+ {
+ newCval = new XElement(C.val,
+ new XElement(C.numRef,
+ sheetName != null ?
+ new XElement(C.f, string.Format("{0}!${2}$2:${2}${1}", sheetName, chartData.CategoryNames.Length + 1, SpreadsheetMLUtil.IntToColumnId(si + 1))) : null,
+ new XElement(C.numCache,
+ sheetName != null ? numRef.Descendants(C.formatCode) : null,
+ new XElement(C.ptCount, new XAttribute("val", chartData.CategoryNames.Length)),
+ chartData.CategoryNames.Select((string cn, int ci) =>
+ {
+ var newPt = new XElement(C.pt,
+ new XAttribute("idx", ci),
+ new XElement(C.v, chartData.Values[si][ci]));
+ return newPt;
+ }))));
+ }
+
+ var serHasFormula = firstSeries.Descendants(C.f).Any();
+ XElement tx = null;
+ if (serHasFormula)
+ {
+ XElement newFormula = null;
+ if (sheetName != null)
+ newFormula = new XElement(C.f, string.Format("{0}!${1}$1", sheetName, SpreadsheetMLUtil.IntToColumnId(si + 1)));
+ tx = new XElement(C.tx,
+ new XElement(C.strRef,
+ newFormula,
+ new XElement(C.strCache,
+ new XElement(C.ptCount, new XAttribute("val", 1)),
+ new XElement(C.pt,
+ new XAttribute("idx", 0),
+ new XElement(C.v, chartData.SeriesNames[si])))));
+ }
+ else
+ {
+ tx = new XElement(C.tx,
+ new XElement(C.v, chartData.SeriesNames[si]));
+ }
+
+ XElement newSer = null;
+
+ if (chartType == C.area3DChart || chartType == C.areaChart)
+ {
+ newSer = new XElement(C.ser,
+ // common
+ new XElement(C.idx, new XAttribute("val", si)),
+ new XElement(C.order, new XAttribute("val", si)),
+ tx,
+ firstSeries.Elements(C.spPr),
+
+ // CT_AreaSer
+ firstSeries.Elements(C.pictureOptions),
+ firstSeries.Elements(C.dPt),
+ firstSeries.Elements(C.dLbls),
+ firstSeries.Elements(C.trendline),
+ firstSeries.Elements(C.errBars),
+ cat,
+ newCval,
+ firstSeries.Elements(C.extLst));
+ }
+ else if (chartType == C.bar3DChart || chartType == C.barChart)
+ {
+ newSer = new XElement(C.ser,
+ // common
+ new XElement(C.idx, new XAttribute("val", si)),
+ new XElement(C.order, new XAttribute("val", si)),
+ tx,
+ firstSeries.Elements(C.spPr),
+
+ // CT_BarSer
+ firstSeries.Elements(C.invertIfNegative),
+ firstSeries.Elements(C.pictureOptions),
+ firstSeries.Elements(C.dPt),
+ firstSeries.Elements(C.dLbls),
+ firstSeries.Elements(C.trendline),
+ firstSeries.Elements(C.errBars),
+ cat,
+ newCval,
+ firstSeries.Elements(C.shape),
+ firstSeries.Elements(C.extLst));
+ }
+ else if (chartType == C.line3DChart || chartType == C.lineChart || chartType == C.stockChart)
+ {
+ newSer = new XElement(C.ser,
+ // common
+ new XElement(C.idx, new XAttribute("val", si)),
+ new XElement(C.order, new XAttribute("val", si)),
+ tx,
+ firstSeries.Elements(C.spPr),
+
+ // CT_LineSer
+ firstSeries.Elements(C.marker),
+ firstSeries.Elements(C.dPt),
+ firstSeries.Elements(C.dLbls),
+ firstSeries.Elements(C.trendline),
+ firstSeries.Elements(C.errBars),
+ cat,
+ newCval,
+ firstSeries.Elements(C.smooth),
+ firstSeries.Elements(C.extLst));
+ }
+ else if (chartType == C.doughnutChart || chartType == C.ofPieChart || chartType == C.pie3DChart || chartType == C.pieChart)
+ {
+ newSer = new XElement(C.ser,
+ // common
+ new XElement(C.idx, new XAttribute("val", si)),
+ new XElement(C.order, new XAttribute("val", si)),
+ tx,
+ firstSeries.Elements(C.spPr),
+
+ // CT_PieSer
+ firstSeries.Elements(C.explosion),
+ firstSeries.Elements(C.dPt),
+ firstSeries.Elements(C.dLbls),
+ cat,
+ newCval,
+ firstSeries.Elements(C.extLst));
+ }
+ else if (chartType == C.surface3DChart || chartType == C.surfaceChart)
+ {
+ newSer = new XElement(C.ser,
+ // common
+ new XElement(C.idx, new XAttribute("val", si)),
+ new XElement(C.order, new XAttribute("val", si)),
+ tx,
+ firstSeries.Elements(C.spPr),
+
+ // CT_SurfaceSer
+ cat,
+ newCval,
+ firstSeries.Elements(C.extLst));
+ }
+
+ if (newSer == null)
+ throw new OpenXmlPowerToolsException("Unsupported chart type");
+
+ int accentNumber = (si % 6) + 1;
+ newSer = (XElement)UpdateAccentTransform(newSer, accentNumber);
+ return newSer;
+ });
+ firstSeries.ReplaceWith(newSetOfSeries);
+ chartPart.PutXDocument();
+ }
+
+ private static void UpdateEmbeddedWorkbook(ChartPart chartPart, ChartData chartData)
+ {
+ XDocument cpXDoc = chartPart.GetXDocument();
+ XElement root = cpXDoc.Root;
+ var firstSeries = root.Descendants(C.ser).FirstOrDefault();
+ if (firstSeries == null)
+ return;
+ var firstFormula = (string)firstSeries.Descendants(C.f).FirstOrDefault();
+ if (firstFormula == null)
+ return;
+ var sheet = firstFormula.Split('!')[0];
+ var embeddedSpreadsheetRid = (string)root.Descendants(C.externalData).Attributes(R.id).FirstOrDefault();
+ if (embeddedSpreadsheetRid == null)
+ return;
+ var embeddedSpreadsheet = chartPart.GetPartById(embeddedSpreadsheetRid);
+ if (embeddedSpreadsheet != null)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(embeddedSpreadsheet.GetStream(), true))
+ {
+ var workbookPart = sDoc.WorkbookPart;
+ var wbRoot = workbookPart.GetXDocument().Root;
+ var sheetRid = (string)wbRoot
+ .Elements(S.sheets)
+ .Elements(S.sheet)
+ .Where(s => (string)s.Attribute("name") == sheet)
+ .Attributes(R.id)
+ .FirstOrDefault();
+ if (sheetRid != null)
+ {
+ var sheetPart = workbookPart.GetPartById(sheetRid);
+ var xdSheet = sheetPart.GetXDocument();
+ var sheetData = xdSheet.Descendants(S.sheetData).FirstOrDefault();
+
+ var stylePart = workbookPart.WorkbookStylesPart;
+ var xdStyle = stylePart.GetXDocument();
+
+ int categoryStyleId = 0;
+ if (chartData.CategoryFormatCode != 0)
+ categoryStyleId = AddDxfToDxfs(xdSheet, xdStyle, chartData.CategoryFormatCode);
+ stylePart.PutXDocument();
+
+ var firstRow = new XElement(S.row,
+ new XAttribute("r", "1"),
+ new XAttribute("spans", string.Format("1:{0}", chartData.SeriesNames.Length + 1)),
+ new [] { new XElement(S.c,
+ new XAttribute("r", "A1"),
+ new XAttribute("t", "str"),
+ new XElement(S.v,
+ new XAttribute(XNamespace.Xml + "space", "preserve"),
+ " "))}
+ .Concat(
+ chartData.SeriesNames
+ .Select((sn, i) => new XElement(S.c,
+ new XAttribute("r", RowColToString(0, i + 1)),
+ new XAttribute("t", "str"),
+ new XElement(S.v, sn)))));
+ var otherRows = chartData
+ .CategoryNames
+ .Select((cn, r) =>
+ {
+ var row = new XElement(S.row,
+ new XAttribute("r", r + 2),
+ new XAttribute("spans", string.Format("1:{0}", chartData.SeriesNames.Length + 1)),
+ new[] {
+ new XElement(S.c,
+ new XAttribute("r", RowColToString(r + 1, 0)),
+ categoryStyleId != 0 ? new XAttribute("s", categoryStyleId) : null,
+ chartData.CategoryDataType == ChartDataType.String ? new XAttribute("t", "str") : null,
+ new XElement(S.v, cn))
+ }.Concat(
+ Enumerable.Range(0, chartData.Values.Length)
+ .Select((c, ci) =>
+ {
+ var cell = new XElement(S.c,
+ new XAttribute("r", RowColToString(r + 1, ci + 1)),
+ new XElement(S.v, chartData.Values[ci][r]));
+ return cell;
+ })));
+ return row;
+ });
+ var allRows = new[] {
+ firstRow
+ }.Concat(otherRows);
+ var newSheetData = new XElement(S.sheetData,
+ allRows);
+ sheetData.ReplaceWith(newSheetData);
+ sheetPart.PutXDocument();
+
+ var tablePartRid = (string)xdSheet
+ .Root
+ .Elements(S.tableParts)
+ .Elements(S.tablePart)
+ .Attributes(R.id)
+ .FirstOrDefault();
+ if (tablePartRid != null)
+ {
+ var partTable = sheetPart.GetPartById(tablePartRid);
+ var xdTablePart = partTable.GetXDocument();
+ var xaRef = xdTablePart.Root.Attribute("ref");
+ xaRef.Value = string.Format("A1:{0}", RowColToString(chartData.CategoryNames.Length - 1, chartData.SeriesNames.Length));
+ var xeNewTableColumns = new XElement(S.tableColumns,
+ new XAttribute("count", chartData.SeriesNames.Count() + 1),
+ new[] {
+ new XElement(S.tableColumn,
+ new XAttribute("id", 1),
+ new XAttribute("name", " "))
+ }.Concat(
+ chartData.SeriesNames.Select((cn, ci) =>
+ new XElement(S.tableColumn,
+ new XAttribute("id", ci + 2),
+ new XAttribute("name", cn)))));
+ var xeExistingTableColumns = xdTablePart.Root.Element(S.tableColumns);
+ if (xeExistingTableColumns != null)
+ xeExistingTableColumns.ReplaceWith(xeNewTableColumns);
+ partTable.PutXDocument();
+ }
+ }
+ }
+ }
+ }
+
+ private static int AddDxfToDxfs(XDocument xdSheet, XDocument xdStyle, int formatCodeToAdd)
+ {
+ // add xf to cellXfs
+ var cellXfs = xdStyle
+ .Root
+ .Element(S.cellXfs);
+ if (cellXfs == null)
+ {
+ var cellStyleXfs = xdStyle
+ .Root
+ .Element(S.cellStyleXfs);
+ if (cellStyleXfs != null)
+ {
+ cellStyleXfs.AddAfterSelf(
+ new XElement(S.cellXfs,
+ new XAttribute("count", 0)));
+ cellXfs = xdSheet
+ .Root
+ .Element(S.cellXfs);
+ }
+ }
+ if (cellXfs == null)
+ {
+ var borders = xdStyle
+ .Root
+ .Element(S.borders);
+ if (borders != null)
+ {
+ borders.AddAfterSelf(
+ new XElement(S.cellXfs,
+ new XAttribute("count", 0)));
+ cellXfs = xdSheet
+ .Root
+ .Element(S.cellXfs);
+ }
+ }
+ if (cellXfs == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ var cnt = (int)cellXfs.Attribute("count");
+ cnt++;
+ cellXfs.Attribute("count").Value = cnt.ToString();
+ cellXfs.Add(
+ new XElement(S.xf,
+ new XAttribute("numFmtId", formatCodeToAdd),
+ new XAttribute("fontId", 0),
+ new XAttribute("fillId", 0),
+ new XAttribute("borderId", 0),
+ new XAttribute("applyNumberFormat", 1)));
+ return cnt - 1;
+ }
+
+ private static string RowColToString(int row, int col)
+ {
+ var str = SpreadsheetMLUtil.IntToColumnId(col) + (row + 1).ToString();
+ return str;
+ }
+
+ private static object UpdateAccentTransform(XNode node, int accentNumber)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == A.schemeClr && (string)element.Attribute("val") == "accent1")
+ return new XElement(A.schemeClr, new XAttribute("val", "accent" + accentNumber));
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => UpdateAccentTransform(n, accentNumber)));
+ }
+ return node;
+ }
+
+ public static bool UpdateChart(PresentationDocument pDoc, int slideNumber, ChartData chartData)
+ {
+ var presentationPart = pDoc.PresentationPart;
+ var pXDoc = presentationPart.GetXDocument();
+ var sldIdElement = pXDoc.Root.Elements(P.sldIdLst).Elements(P.sldId).Skip(slideNumber - 1).FirstOrDefault();
+ if (sldIdElement != null)
+ {
+ var rId = (string)sldIdElement.Attribute(R.id);
+ var slidePart = presentationPart.GetPartById(rId);
+ var sXDoc = slidePart.GetXDocument();
+ var chartRid = (string)sXDoc.Descendants(C.chart).Attributes(R.id).FirstOrDefault();
+ if (chartRid != null)
+ {
+ ChartPart chartPart = (ChartPart)slidePart.GetPartById(chartRid);
+ UpdateChart(chartPart, chartData);
+ return true;
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenXmlPowerTools/DocumentAssembler.cs b/OpenXmlPowerTools/DocumentAssembler.cs
new file mode 100644
index 0000000..6f505c9
--- /dev/null
+++ b/OpenXmlPowerTools/DocumentAssembler.cs
@@ -0,0 +1,872 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.XPath;
+using System.Xml.Schema;
+using DocumentFormat.OpenXml.Office.CustomUI;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using System.Collections;
+
+namespace OpenXmlPowerTools
+{
+ public class DocumentAssembler
+ {
+ public static WmlDocument AssembleDocument(WmlDocument templateDoc, XmlDocument data, out bool templateError)
+ {
+ XDocument xDoc = data.GetXDocument();
+ return AssembleDocument(templateDoc, xDoc.Root, out templateError);
+ }
+
+ public static WmlDocument AssembleDocument(WmlDocument templateDoc, XElement data, out bool templateError)
+ {
+ byte[] byteArray = templateDoc.DocumentByteArray;
+ using (MemoryStream mem = new MemoryStream())
+ {
+ mem.Write(byteArray, 0, (int)byteArray.Length);
+ using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
+ {
+ if (RevisionAccepter.HasTrackedRevisions(wordDoc))
+ throw new OpenXmlPowerToolsException("Invalid DocumentAssembler template - contains tracked revisions");
+
+ var te = new TemplateError();
+ foreach (var part in wordDoc.ContentParts())
+ {
+ ProcessTemplatePart(data, te, part);
+ }
+ templateError = te.HasError;
+ }
+ WmlDocument assembledDocument = new WmlDocument("TempFileName.docx", mem.ToArray());
+ return assembledDocument;
+ }
+ }
+
+ private static void ProcessTemplatePart(XElement data, TemplateError te, OpenXmlPart part)
+ {
+ XDocument xDoc = part.GetXDocument();
+
+ var xDocRoot = RemoveGoBackBookmarks(xDoc.Root);
+
+ // content controls in cells can surround the W.tc element, so transform so that such content controls are within the cell content
+ xDocRoot = (XElement)NormalizeContentControlsInCells(xDocRoot);
+
+ xDocRoot = (XElement)TransformToMetadata(xDocRoot, data, te);
+
+ // Table might have been placed at run-level, when it should be at block-level, so fix this.
+ // Repeat, EndRepeat, Conditional, EndConditional are allowed at run level, but only if there is a matching pair
+ // if there is only one Repeat, EndRepeat, Conditional, EndConditional, then move to block level.
+ // if there is a matching pair, then is OK.
+ xDocRoot = (XElement)ForceBlockLevelAsAppropriate(xDocRoot, te);
+
+ NormalizeTablesRepeatAndConditional(xDocRoot, te);
+
+ // any EndRepeat, EndConditional that remain are orphans, so replace with an error
+ ProcessOrphanEndRepeatEndConditional(xDocRoot, te);
+
+ // do the actual content replacement
+ xDocRoot = (XElement)ContentReplacementTransform(xDocRoot, data, te);
+
+ xDoc.Elements().First().ReplaceWith(xDocRoot);
+ part.PutXDocument();
+ return;
+ }
+
+ private static XName[] s_MetaToForceToBlock = new XName[] {
+ PA.Conditional,
+ PA.EndConditional,
+ PA.Repeat,
+ PA.EndRepeat,
+ PA.Table,
+ };
+
+ private static object ForceBlockLevelAsAppropriate(XNode node, TemplateError te)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ {
+ var childMeta = element.Elements().Where(n => s_MetaToForceToBlock.Contains(n.Name)).ToList();
+ if (childMeta.Count() == 1)
+ {
+ var child = childMeta.First();
+ var otherTextInParagraph = element.Elements(W.r).Elements(W.t).Select(t => (string)t).StringConcatenate().Trim();
+ if (otherTextInParagraph != "")
+ {
+ var newPara = new XElement(element);
+ var newMeta = newPara.Elements().Where(n => s_MetaToForceToBlock.Contains(n.Name)).First();
+ newMeta.ReplaceWith(CreateRunErrorMessage("Error: Unmatched metadata can't be in paragraph with other text", te));
+ return newPara;
+ }
+ var meta = new XElement(child.Name,
+ child.Attributes(),
+ new XElement(W.p,
+ element.Attributes(),
+ element.Elements(W.pPr),
+ child.Elements()));
+ return meta;
+ }
+ var count = childMeta.Count();
+ if (count % 2 == 0)
+ {
+ if (childMeta.Where(c => c.Name == PA.Repeat).Count() != childMeta.Where(c => c.Name == PA.EndRepeat).Count())
+ return CreateContextErrorMessage(element, "Error: Mismatch Repeat / EndRepeat at run level", te);
+ if (childMeta.Where(c => c.Name == PA.Conditional).Count() != childMeta.Where(c => c.Name == PA.EndConditional).Count())
+ return CreateContextErrorMessage(element, "Error: Mismatch Conditional / EndConditional at run level", te);
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => ForceBlockLevelAsAppropriate(n, te)));
+ }
+ else
+ {
+ return CreateContextErrorMessage(element, "Error: Invalid metadata at run level", te);
+ }
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => ForceBlockLevelAsAppropriate(n, te)));
+ }
+ return node;
+ }
+
+ private static void ProcessOrphanEndRepeatEndConditional(XElement xDocRoot, TemplateError te)
+ {
+ foreach (var element in xDocRoot.Descendants(PA.EndRepeat).ToList())
+ {
+ var error = CreateContextErrorMessage(element, "Error: EndRepeat without matching Repeat", te);
+ element.ReplaceWith(error);
+ }
+ foreach (var element in xDocRoot.Descendants(PA.EndConditional).ToList())
+ {
+ var error = CreateContextErrorMessage(element, "Error: EndConditional without matching Conditional", te);
+ element.ReplaceWith(error);
+ }
+ }
+
+ private static XElement RemoveGoBackBookmarks(XElement xElement)
+ {
+ var cloneXDoc = new XElement(xElement);
+ while (true)
+ {
+ var bm = cloneXDoc.DescendantsAndSelf(W.bookmarkStart).FirstOrDefault(b => (string)b.Attribute(W.name) == "_GoBack");
+ if (bm == null)
+ break;
+ var id = (string)bm.Attribute(W.id);
+ var endBm = cloneXDoc.DescendantsAndSelf(W.bookmarkEnd).FirstOrDefault(b => (string)b.Attribute(W.id) == id);
+ bm.Remove();
+ endBm.Remove();
+ }
+ return cloneXDoc;
+ }
+
+ // this transform inverts content controls that surround W.tc elements. After transforming, the W.tc will contain
+ // the content control, which contains the paragraph content of the cell.
+ private static object NormalizeContentControlsInCells(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.sdt && element.Parent.Name == W.tr)
+ {
+ var newCell = new XElement(W.tc,
+ element.Elements(W.tc).Elements(W.tcPr),
+ new XElement(W.sdt,
+ element.Elements(W.sdtPr),
+ element.Elements(W.sdtEndPr),
+ new XElement(W.sdtContent,
+ element.Elements(W.sdtContent).Elements(W.tc).Elements().Where(e => e.Name != W.tcPr))));
+ return newCell;
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => NormalizeContentControlsInCells(n)));
+ }
+ return node;
+ }
+
+ // The following method is written using tree modification, not RPFT, because it is easier to write in this fashion.
+ // These types of operations are not as easy to write using RPFT.
+ // Unless you are completely clear on the semantics of LINQ to XML DML, do not make modifications to this method.
+ private static void NormalizeTablesRepeatAndConditional(XElement xDoc, TemplateError te)
+ {
+ var tables = xDoc.Descendants(PA.Table).ToList();
+ foreach (var table in tables)
+ {
+ var followingElement = table.ElementsAfterSelf().Where(e => e.Name == W.tbl || e.Name == W.p).FirstOrDefault();
+ if (followingElement == null || followingElement.Name != W.tbl)
+ {
+ table.ReplaceWith(CreateParaErrorMessage("Table metadata is not immediately followed by a table", te));
+ continue;
+ }
+ // remove superflous paragraph from Table metadata
+ table.RemoveNodes();
+ // detach w:tbl from parent, and add to Table metadata
+ followingElement.Remove();
+ table.Add(followingElement);
+ }
+
+ int repeatDepth = 0;
+ int conditionalDepth = 0;
+ foreach (var metadata in xDoc.Descendants().Where(d =>
+ d.Name == PA.Repeat ||
+ d.Name == PA.Conditional ||
+ d.Name == PA.EndRepeat ||
+ d.Name == PA.EndConditional))
+ {
+ if (metadata.Name == PA.Repeat)
+ {
+ ++repeatDepth;
+ metadata.Add(new XAttribute(PA.Depth, repeatDepth));
+ continue;
+ }
+ if (metadata.Name == PA.EndRepeat)
+ {
+ metadata.Add(new XAttribute(PA.Depth, repeatDepth));
+ --repeatDepth;
+ continue;
+ }
+ if (metadata.Name == PA.Conditional)
+ {
+ ++conditionalDepth;
+ metadata.Add(new XAttribute(PA.Depth, conditionalDepth));
+ continue;
+ }
+ if (metadata.Name == PA.EndConditional)
+ {
+ metadata.Add(new XAttribute(PA.Depth, conditionalDepth));
+ --conditionalDepth;
+ continue;
+ }
+ }
+
+ while (true)
+ {
+ bool didReplace = false;
+ foreach (var metadata in xDoc.Descendants().Where(d => (d.Name == PA.Repeat || d.Name == PA.Conditional) && d.Attribute(PA.Depth) != null).ToList())
+ {
+ var depth = (int)metadata.Attribute(PA.Depth);
+ XName matchingEndName = null;
+ if (metadata.Name == PA.Repeat)
+ matchingEndName = PA.EndRepeat;
+ else if (metadata.Name == PA.Conditional)
+ matchingEndName = PA.EndConditional;
+ if (matchingEndName == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var matchingEnd = metadata.ElementsAfterSelf(matchingEndName).FirstOrDefault(end => { return (int)end.Attribute(PA.Depth) == depth; });
+ if (matchingEnd == null)
+ {
+ metadata.ReplaceWith(CreateParaErrorMessage(string.Format("{0} does not have matching {1}", metadata.Name.LocalName, matchingEndName.LocalName), te));
+ continue;
+ }
+ metadata.RemoveNodes();
+ var contentBetween = metadata.ElementsAfterSelf().TakeWhile(after => after != matchingEnd).ToList();
+ foreach (var item in contentBetween)
+ item.Remove();
+ contentBetween = contentBetween.Where(n => n.Name != W.bookmarkStart && n.Name != W.bookmarkEnd).ToList();
+ metadata.Add(contentBetween);
+ metadata.Attributes(PA.Depth).Remove();
+ matchingEnd.Remove();
+ didReplace = true;
+ break;
+ }
+ if (!didReplace)
+ break;
+ }
+ }
+
+ private static List<string> s_AliasList = new List<string>()
+ {
+ "Content",
+ "Table",
+ "Repeat",
+ "EndRepeat",
+ "Conditional",
+ "EndConditional",
+ };
+
+ private static object TransformToMetadata(XNode node, XElement data, TemplateError te)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.sdt)
+ {
+ var alias = (string)element.Elements(W.sdtPr).Elements(W.alias).Attributes(W.val).FirstOrDefault();
+ if (alias == null || alias == "" || s_AliasList.Contains(alias))
+ {
+ var ccContents = element
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate()
+ .Trim()
+ .Replace('“', '"')
+ .Replace('”', '"');
+ if (ccContents.StartsWith("<"))
+ {
+ XElement xml = TransformXmlTextToMetadata(te, ccContents);
+ if (xml.Name == W.p || xml.Name == W.r) // this means there was an error processing the XML.
+ {
+ if (element.Parent.Name == W.p)
+ return xml.Elements(W.r);
+ return xml;
+ }
+ if (alias != null && xml.Name.LocalName != alias)
+ {
+ if (element.Parent.Name == W.p)
+ return CreateRunErrorMessage("Error: Content control alias does not match metadata element name", te);
+ else
+ return CreateParaErrorMessage("Error: Content control alias does not match metadata element name", te);
+ }
+ xml.Add(element.Elements(W.sdtContent).Elements());
+ return xml;
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformToMetadata(n, data, te)));
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformToMetadata(n, data, te)));
+ }
+ if (element.Name == W.p)
+ {
+ var paraContents = element
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate()
+ .Trim();
+ int occurances = paraContents.Select((c, i) => paraContents.Substring(i)).Count(sub => sub.StartsWith("<#"));
+ if (paraContents.StartsWith("<#") && paraContents.EndsWith("#>") && occurances == 1)
+ {
+ var xmlText = paraContents.Substring(2, paraContents.Length - 4).Trim();
+ XElement xml = TransformXmlTextToMetadata(te, xmlText);
+ if (xml.Name == W.p || xml.Name == W.r)
+ return xml;
+ xml.Add(element);
+ return xml;
+ }
+ if (paraContents.Contains("<#"))
+ {
+ List<RunReplacementInfo> runReplacementInfo = new List<RunReplacementInfo>();
+ var thisGuid = Guid.NewGuid().ToString();
+ var r = new Regex("<#.*?#>");
+ XElement xml = null;
+ OpenXmlRegex.Replace(new[] { element }, r, thisGuid, (para, match) =>
+ {
+ var matchString = match.Value.Trim();
+ var xmlText = matchString.Substring(2, matchString.Length - 4).Trim().Replace('“', '"').Replace('”', '"');
+ try
+ {
+ xml = XElement.Parse(xmlText);
+ }
+ catch (XmlException e)
+ {
+ RunReplacementInfo rri = new RunReplacementInfo()
+ {
+ Xml = null,
+ XmlExceptionMessage = "XmlException: " + e.Message,
+ SchemaValidationMessage = null,
+ };
+ runReplacementInfo.Add(rri);
+ return true;
+ }
+ string schemaError = ValidatePerSchema(xml);
+ if (schemaError != null)
+ {
+ RunReplacementInfo rri = new RunReplacementInfo()
+ {
+ Xml = null,
+ XmlExceptionMessage = null,
+ SchemaValidationMessage = "Schema Validation Error: " + schemaError,
+ };
+ runReplacementInfo.Add(rri);
+ return true;
+ }
+ RunReplacementInfo rri2 = new RunReplacementInfo()
+ {
+ Xml = xml,
+ XmlExceptionMessage = null,
+ SchemaValidationMessage = null,
+ };
+ runReplacementInfo.Add(rri2);
+ return true;
+ }, false);
+
+ var newPara = new XElement(element);
+ foreach (var rri in runReplacementInfo)
+ {
+ var runToReplace = newPara.Descendants(W.r).FirstOrDefault(rn => rn.Value == thisGuid && rn.Parent.Name != PA.Content);
+ if (runToReplace == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ if (rri.XmlExceptionMessage != null)
+ runToReplace.ReplaceWith(CreateRunErrorMessage(rri.XmlExceptionMessage, te));
+ else if (rri.SchemaValidationMessage != null)
+ runToReplace.ReplaceWith(CreateRunErrorMessage(rri.SchemaValidationMessage, te));
+ else
+ {
+ var newXml = new XElement(rri.Xml);
+ newXml.Add(runToReplace);
+ runToReplace.ReplaceWith(newXml);
+ }
+ }
+ var coalescedParagraph = WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(newPara);
+ return coalescedParagraph;
+ }
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformToMetadata(n, data, te)));
+ }
+ return node;
+ }
+
+ private static XElement TransformXmlTextToMetadata(TemplateError te, string xmlText)
+ {
+ XElement xml;
+ try
+ {
+ xml = XElement.Parse(xmlText);
+ }
+ catch (XmlException e)
+ {
+ return CreateParaErrorMessage("XmlException: " + e.Message, te);
+ }
+ string schemaError = ValidatePerSchema(xml);
+ if (schemaError != null)
+ return CreateParaErrorMessage("Schema Validation Error: " + schemaError, te);
+ return xml;
+ }
+
+ private class RunReplacementInfo
+ {
+ public XElement Xml;
+ public string XmlExceptionMessage;
+ public string SchemaValidationMessage;
+ }
+
+ private static string ValidatePerSchema(XElement element)
+ {
+ if (s_PASchemaSets == null)
+ {
+ s_PASchemaSets = new Dictionary<XName, PASchemaSet>()
+ {
+ {
+ PA.Content,
+ new PASchemaSet() {
+ XsdMarkup =
+ @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
+ <xs:element name='Content'>
+ <xs:complexType>
+ <xs:attribute name='Select' type='xs:string' use='required' />
+ <xs:attribute name='Optional' type='xs:boolean' use='optional' />
+ </xs:complexType>
+ </xs:element>
+ </xs:schema>",
+ }
+ },
+ {
+ PA.Table,
+ new PASchemaSet() {
+ XsdMarkup =
+ @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
+ <xs:element name='Table'>
+ <xs:complexType>
+ <xs:attribute name='Select' type='xs:string' use='required' />
+ </xs:complexType>
+ </xs:element>
+ </xs:schema>",
+ }
+ },
+ {
+ PA.Repeat,
+ new PASchemaSet() {
+ XsdMarkup =
+ @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
+ <xs:element name='Repeat'>
+ <xs:complexType>
+ <xs:attribute name='Select' type='xs:string' use='required' />
+ <xs:attribute name='Optional' type='xs:boolean' use='optional' />
+ </xs:complexType>
+ </xs:element>
+ </xs:schema>",
+ }
+ },
+ {
+ PA.EndRepeat,
+ new PASchemaSet() {
+ XsdMarkup =
+ @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
+ <xs:element name='EndRepeat' />
+ </xs:schema>",
+ }
+ },
+ {
+ PA.Conditional,
+ new PASchemaSet() {
+ XsdMarkup =
+ @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
+ <xs:element name='Conditional'>
+ <xs:complexType>
+ <xs:attribute name='Select' type='xs:string' use='required' />
+ <xs:attribute name='Match' type='xs:string' use='optional' />
+ <xs:attribute name='NotMatch' type='xs:string' use='optional' />
+ </xs:complexType>
+ </xs:element>
+ </xs:schema>",
+ }
+ },
+ {
+ PA.EndConditional,
+ new PASchemaSet() {
+ XsdMarkup =
+ @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
+ <xs:element name='EndConditional' />
+ </xs:schema>",
+ }
+ },
+ };
+ foreach (var item in s_PASchemaSets)
+ {
+ var itemPAss = item.Value;
+ XmlSchemaSet schemas = new XmlSchemaSet();
+ schemas.Add("", XmlReader.Create(new StringReader(itemPAss.XsdMarkup)));
+ itemPAss.SchemaSet = schemas;
+ }
+ }
+ if (!s_PASchemaSets.ContainsKey(element.Name))
+ {
+ return string.Format("Invalid XML: {0} is not a valid element", element.Name.LocalName);
+ }
+ var paSchemaSet = s_PASchemaSets[element.Name];
+ XDocument d = new XDocument(element);
+ string message = null;
+ d.Validate(paSchemaSet.SchemaSet, (sender, e) =>
+ {
+ if (message == null)
+ message = e.Message;
+ }, true);
+ if (message != null)
+ return message;
+ return null;
+ }
+
+ private class PA
+ {
+ public static XName Content = "Content";
+ public static XName Table = "Table";
+ public static XName Repeat = "Repeat";
+ public static XName EndRepeat = "EndRepeat";
+ public static XName Conditional = "Conditional";
+ public static XName EndConditional = "EndConditional";
+
+ public static XName Select = "Select";
+ public static XName Optional = "Optional";
+ public static XName Match = "Match";
+ public static XName NotMatch = "NotMatch";
+ public static XName Depth = "Depth";
+ }
+
+ private class PASchemaSet
+ {
+ public string XsdMarkup;
+ public XmlSchemaSet SchemaSet;
+ }
+
+ private static Dictionary<XName, PASchemaSet> s_PASchemaSets = null;
+
+ private class TemplateError
+ {
+ public bool HasError = false;
+ }
+
+ static object ContentReplacementTransform(XNode node, XElement data, TemplateError templateError)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == PA.Content)
+ {
+ XElement para = element.Descendants(W.p).FirstOrDefault();
+ XElement run = element.Descendants(W.r).FirstOrDefault();
+
+ var xPath = (string) element.Attribute(PA.Select);
+ var optionalString = (string) element.Attribute(PA.Optional);
+ bool optional = (optionalString != null && optionalString.ToLower() == "true");
+
+ string newValue;
+ try
+ {
+ newValue = EvaluateXPathToString(data, xPath, optional);
+ }
+ catch (XPathException e)
+ {
+ return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
+ }
+
+ if (para != null)
+ {
+
+ XElement p = new XElement(W.p, para.Elements(W.pPr));
+ foreach(string line in newValue.Split('\n'))
+ {
+ p.Add(new XElement(W.r,
+ para.Elements(W.r).Elements(W.rPr).FirstOrDefault(),
+ (p.Elements().Count() > 1) ? new XElement(W.br) : null,
+ new XElement(W.t, line)));
+ }
+ return p;
+ }
+ else
+ {
+ List<XElement> list = new List<XElement>();
+ foreach(string line in newValue.Split('\n'))
+ {
+ list.Add(new XElement(W.r,
+ run.Elements().Where(e => e.Name != W.t),
+ (list.Count > 0) ? new XElement(W.br) : null,
+ new XElement(W.t, line)));
+ }
+ return list;
+ }
+ }
+ if (element.Name == PA.Repeat)
+ {
+ string selector = (string)element.Attribute(PA.Select);
+ var optionalString = (string)element.Attribute(PA.Optional);
+ bool optional = (optionalString != null && optionalString.ToLower() == "true");
+
+ IEnumerable<XElement> repeatingData;
+ try
+ {
+ repeatingData = data.XPathSelectElements(selector);
+ }
+ catch (XPathException e)
+ {
+ return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
+ }
+ if (!repeatingData.Any())
+ {
+ if (optional)
+ {
+ return null;
+ //XElement para = element.Descendants(W.p).FirstOrDefault();
+ //if (para != null)
+ // return new XElement(W.p, new XElement(W.r));
+ //else
+ // return new XElement(W.r);
+ }
+ return CreateContextErrorMessage(element, "Repeat: Select returned no data", templateError);
+ }
+ var newContent = repeatingData.Select(d =>
+ {
+ var content = element
+ .Elements()
+ .Select(e => ContentReplacementTransform(e, d, templateError))
+ .ToList();
+ return content;
+ })
+ .ToList();
+ return newContent;
+ }
+ if (element.Name == PA.Table)
+ {
+ IEnumerable<XElement> tableData;
+ try
+ {
+ tableData = data.XPathSelectElements((string)element.Attribute(PA.Select));
+ }
+ catch (XPathException e)
+ {
+ return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
+ }
+ if (tableData.Count() == 0)
+ return CreateContextErrorMessage(element, "Table Select returned no data", templateError);
+ XElement table = element.Element(W.tbl);
+ XElement protoRow = table.Elements(W.tr).Skip(1).FirstOrDefault();
+ var footerRowsBeforeTransform = table
+ .Elements(W.tr)
+ .Skip(2)
+ .ToList();
+ var footerRows = footerRowsBeforeTransform
+ .Select(x => ContentReplacementTransform(x, data, templateError))
+ .ToList();
+ if (protoRow == null)
+ return CreateContextErrorMessage(element, string.Format("Table does not contain a prototype row"), templateError);
+ protoRow.Descendants(W.bookmarkStart).Remove();
+ protoRow.Descendants(W.bookmarkEnd).Remove();
+ XElement newTable = new XElement(W.tbl,
+ table.Elements().Where(e => e.Name != W.tr),
+ table.Elements(W.tr).FirstOrDefault(),
+ tableData.Select(d =>
+ new XElement(W.tr,
+ protoRow.Elements().Where(r => r.Name != W.tc),
+ protoRow.Elements(W.tc)
+ .Select(tc =>
+ {
+ XElement paragraph = tc.Elements(W.p).FirstOrDefault();
+ XElement cellRun = paragraph.Elements(W.r).FirstOrDefault();
+ string xPath = paragraph.Value;
+ string newValue = null;
+ try
+ {
+ newValue = EvaluateXPathToString(d, xPath, false);
+ }
+ catch (XPathException e)
+ {
+ XElement errorCell = new XElement(W.tc,
+ tc.Elements().Where(z => z.Name != W.p),
+ new XElement(W.p,
+ paragraph.Element(W.pPr),
+ CreateRunErrorMessage(e.Message, templateError)));
+ return errorCell;
+ }
+
+ XElement newCell = new XElement(W.tc,
+ tc.Elements().Where(z => z.Name != W.p),
+ new XElement(W.p,
+ paragraph.Element(W.pPr),
+ new XElement(W.r,
+ cellRun != null ? cellRun.Element(W.rPr) : new XElement(W.rPr), //if the cell was empty there is no cellrun
+ new XElement(W.t, newValue))));
+ return newCell;
+ }))),
+ footerRows
+ );
+ return newTable;
+ }
+ if (element.Name == PA.Conditional)
+ {
+ string xPath = (string)element.Attribute(PA.Select);
+ var match = (string)element.Attribute(PA.Match);
+ var notMatch = (string)element.Attribute(PA.NotMatch);
+
+ if (match == null && notMatch == null)
+ return CreateContextErrorMessage(element, "Conditional: Must specify either Match or NotMatch", templateError);
+ if (match != null && notMatch != null)
+ return CreateContextErrorMessage(element, "Conditional: Cannot specify both Match and NotMatch", templateError);
+
+ string testValue = null;
+
+ try
+ {
+ testValue = EvaluateXPathToString(data, xPath, false);
+ }
+ catch (XPathException e)
+ {
+ return CreateContextErrorMessage(element, e.Message, templateError);
+ }
+
+ if ((match != null && testValue == match) || (notMatch != null && testValue != notMatch))
+ {
+ var content = element.Elements().Select(e => ContentReplacementTransform(e, data, templateError));
+ return content;
+ }
+ return null;
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => ContentReplacementTransform(n, data, templateError)));
+ }
+ return node;
+ }
+
+ private static object CreateContextErrorMessage(XElement element, string errorMessage, TemplateError templateError)
+ {
+ XElement para = element.Descendants(W.p).FirstOrDefault();
+ XElement run = element.Descendants(W.r).FirstOrDefault();
+ var errorRun = CreateRunErrorMessage(errorMessage, templateError);
+ if (para != null)
+ return new XElement(W.p, errorRun);
+ else
+ return errorRun;
+ }
+
+ private static XElement CreateRunErrorMessage(string errorMessage, TemplateError templateError)
+ {
+ templateError.HasError = true;
+ var errorRun = new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.color, new XAttribute(W.val, "FF0000")),
+ new XElement(W.highlight, new XAttribute(W.val, "yellow"))),
+ new XElement(W.t, errorMessage));
+ return errorRun;
+ }
+
+ private static XElement CreateParaErrorMessage(string errorMessage, TemplateError templateError)
+ {
+ templateError.HasError = true;
+ var errorPara = new XElement(W.p,
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.color, new XAttribute(W.val, "FF0000")),
+ new XElement(W.highlight, new XAttribute(W.val, "yellow"))),
+ new XElement(W.t, errorMessage)));
+ return errorPara;
+ }
+
+ private static string EvaluateXPathToString(XElement element, string xPath, bool optional )
+ {
+ object xPathSelectResult;
+ try
+ {
+ //support some cells in the table may not have an xpath expression.
+ if (String.IsNullOrWhiteSpace(xPath)) return String.Empty;
+
+ xPathSelectResult = element.XPathEvaluate(xPath);
+ }
+ catch (XPathException e)
+ {
+ throw new XPathException("XPathException: " + e.Message, e);
+ }
+
+ if ((xPathSelectResult is IEnumerable) && !(xPathSelectResult is string))
+ {
+ var selectedData = ((IEnumerable) xPathSelectResult).Cast<XObject>();
+ if (!selectedData.Any())
+ {
+ if (optional) return string.Empty;
+ throw new XPathException(string.Format("XPath expression ({0}) returned no results", xPath));
+ }
+ if (selectedData.Count() > 1)
+ {
+ throw new XPathException(string.Format("XPath expression ({0}) returned more than one node", xPath));
+ }
+
+ XObject selectedDatum = selectedData.First();
+
+ if (selectedDatum is XElement) return ((XElement) selectedDatum).Value;
+
+ if (selectedDatum is XAttribute) return ((XAttribute) selectedDatum).Value;
+ }
+
+ return xPathSelectResult.ToString();
+
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/DocumentBuilder.cs b/OpenXmlPowerTools/DocumentBuilder.cs
new file mode 100644
index 0000000..6af5254
--- /dev/null
+++ b/OpenXmlPowerTools/DocumentBuilder.cs
@@ -0,0 +1,2886 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 2.6.00
+
+***************************************************************************/
+
+#define TestForUnsupportedDocuments
+#define MergeStylesWithSameNames
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public partial class WmlDocument : OpenXmlPowerToolsDocument
+ {
+ public IEnumerable<WmlDocument> SplitOnSections()
+ {
+ return DocumentBuilder.SplitOnSections(this);
+ }
+ }
+
+ public class Source
+ {
+ public WmlDocument WmlDocument { get; set; }
+ public int Start { get; set; }
+ public int Count { get; set; }
+ public bool KeepSections { get; set; }
+ public bool DiscardHeadersAndFootersInKeptSections { get; set; }
+ public string InsertId { get; set; }
+
+ public Source(string fileName)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepSections = false;
+ InsertId = null;
+ }
+
+ public Source(WmlDocument source)
+ {
+ WmlDocument = source;
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepSections = false;
+ InsertId = null;
+ }
+
+ public Source(string fileName, bool keepSections)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepSections = keepSections;
+ InsertId = null;
+ }
+
+ public Source(WmlDocument source, bool keepSections)
+ {
+ WmlDocument = source;
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepSections = keepSections;
+ InsertId = null;
+ }
+
+ public Source(string fileName, string insertId)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepSections = false;
+ InsertId = insertId;
+ }
+
+ public Source(WmlDocument source, string insertId)
+ {
+ WmlDocument = source;
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepSections = false;
+ InsertId = insertId;
+ }
+
+ public Source(string fileName, int start, bool keepSections)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = start;
+ Count = Int32.MaxValue;
+ KeepSections = keepSections;
+ InsertId = null;
+ }
+
+ public Source(WmlDocument source, int start, bool keepSections)
+ {
+ WmlDocument = source;
+ Start = start;
+ Count = Int32.MaxValue;
+ KeepSections = keepSections;
+ InsertId = null;
+ }
+
+ public Source(string fileName, int start, string insertId)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = start;
+ Count = Int32.MaxValue;
+ KeepSections = false;
+ InsertId = insertId;
+ }
+
+ public Source(WmlDocument source, int start, string insertId)
+ {
+ WmlDocument = source;
+ Start = start;
+ Count = Int32.MaxValue;
+ KeepSections = false;
+ InsertId = insertId;
+ }
+
+ public Source(string fileName, int start, int count, bool keepSections)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = start;
+ Count = count;
+ KeepSections = keepSections;
+ InsertId = null;
+ }
+
+ public Source(WmlDocument source, int start, int count, bool keepSections)
+ {
+ WmlDocument = source;
+ Start = start;
+ Count = count;
+ KeepSections = keepSections;
+ InsertId = null;
+ }
+
+ public Source(string fileName, int start, int count, string insertId)
+ {
+ WmlDocument = new WmlDocument(fileName);
+ Start = start;
+ Count = count;
+ KeepSections = false;
+ InsertId = insertId;
+ }
+
+ public Source(WmlDocument source, int start, int count, string insertId)
+ {
+ WmlDocument = source;
+ Start = start;
+ Count = count;
+ KeepSections = false;
+ InsertId = insertId;
+ }
+ }
+
+ public static class DocumentBuilder
+ {
+ public static void BuildDocument(List<Source> sources, string fileName)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreateWordprocessingDocument())
+ {
+ using (WordprocessingDocument output = streamDoc.GetWordprocessingDocument())
+ {
+ BuildDocument(sources, output);
+ output.Close();
+ }
+ streamDoc.GetModifiedDocument().SaveAs(fileName);
+ }
+ }
+
+ public static WmlDocument BuildDocument(List<Source> sources)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreateWordprocessingDocument())
+ {
+ using (WordprocessingDocument output = streamDoc.GetWordprocessingDocument())
+ {
+ BuildDocument(sources, output);
+ output.Close();
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ private struct TempSource
+ {
+ public int Start;
+ public int Count;
+ };
+
+ private class Atbi
+ {
+ public XElement BlockLevelContent;
+ public int Index;
+ }
+
+ private class Atbid
+ {
+ public XElement BlockLevelContent;
+ public int Index;
+ public int Div;
+ }
+
+ public static IEnumerable<WmlDocument> SplitOnSections(WmlDocument doc)
+ {
+ List<TempSource> tempSourceList;
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ XDocument mainDocument = document.MainDocumentPart.GetXDocument();
+ var divs = mainDocument
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Select((p, i) => new Atbi
+ {
+ BlockLevelContent = p,
+ Index = i,
+ })
+ .Rollup(new Atbid
+ {
+ BlockLevelContent = (XElement)null,
+ Index = -1,
+ Div = 0,
+ },
+ (b, p) =>
+ {
+ XElement elementBefore = b.BlockLevelContent
+ .SiblingsBeforeSelfReverseDocumentOrder()
+ .FirstOrDefault();
+ if (elementBefore != null && elementBefore.Descendants(W.sectPr).Any())
+ return new Atbid
+ {
+ BlockLevelContent = b.BlockLevelContent,
+ Index = b.Index,
+ Div = p.Div + 1,
+ };
+ return new Atbid
+ {
+ BlockLevelContent = b.BlockLevelContent,
+ Index = b.Index,
+ Div = p.Div,
+ };
+ });
+ var groups = divs
+ .GroupAdjacent(b => b.Div);
+ tempSourceList = groups
+ .Select(g => new TempSource
+ {
+ Start = g.First().Index,
+ Count = g.Count(),
+ })
+ .ToList();
+ foreach (var ts in tempSourceList)
+ {
+ List<Source> sources = new List<Source>()
+ {
+ new Source(doc, ts.Start, ts.Count, true)
+ };
+ WmlDocument newDoc = DocumentBuilder.BuildDocument(sources);
+ newDoc = AdjustSectionBreak(newDoc);
+ yield return newDoc;
+ }
+ }
+ }
+
+ private static WmlDocument AdjustSectionBreak(WmlDocument doc)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ {
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ XDocument mainXDoc = document.MainDocumentPart.GetXDocument();
+ XElement lastElement = mainXDoc.Root
+ .Element(W.body)
+ .Elements()
+ .LastOrDefault();
+ if (lastElement != null)
+ {
+ if (lastElement.Name != W.sectPr &&
+ lastElement.Descendants(W.sectPr).Any())
+ {
+ mainXDoc.Root.Element(W.body).Add(lastElement.Descendants(W.sectPr).First());
+ lastElement.Descendants(W.sectPr).Remove();
+ if (!lastElement.Elements()
+ .Where(e => e.Name != W.pPr)
+ .Any())
+ lastElement.Remove();
+ document.MainDocumentPart.PutXDocument();
+ }
+ }
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ private static void BuildDocument(List<Source> sources, WordprocessingDocument output)
+ {
+ if (RelationshipMarkup == null)
+ RelationshipMarkup = new Dictionary<XName, XName[]>()
+ {
+ //{ button, new [] { image }},
+ { A.blip, new [] { R.embed, R.link }},
+ { A.hlinkClick, new [] { R.id }},
+ { A.relIds, new [] { R.cs, R.dm, R.lo, R.qs }},
+ //{ a14:imgLayer, new [] { R.embed }},
+ //{ ax:ocx, new [] { R.id }},
+ { C.chart, new [] { R.id }},
+ { C.externalData, new [] { R.id }},
+ { C.userShapes, new [] { R.id }},
+ { DGM.relIds, new [] { R.cs, R.dm, R.lo, R.qs }},
+ { O.OLEObject, new [] { R.id }},
+ { VML.fill, new [] { R.id }},
+ { VML.imagedata, new [] { R.href, R.id, R.pict }},
+ { VML.stroke, new [] { R.id }},
+ { W.altChunk, new [] { R.id }},
+ { W.attachedTemplate, new [] { R.id }},
+ { W.control, new [] { R.id }},
+ { W.dataSource, new [] { R.id }},
+ { W.embedBold, new [] { R.id }},
+ { W.embedBoldItalic, new [] { R.id }},
+ { W.embedItalic, new [] { R.id }},
+ { W.embedRegular, new [] { R.id }},
+ { W.footerReference, new [] { R.id }},
+ { W.headerReference, new [] { R.id }},
+ { W.headerSource, new [] { R.id }},
+ { W.hyperlink, new [] { R.id }},
+ { W.printerSettings, new [] { R.id }},
+ { W.recipientData, new [] { R.id }}, // Mail merge, not required
+ { W.saveThroughXslt, new [] { R.id }},
+ { W.sourceFileName, new [] { R.id }}, // Framesets, not required
+ { W.src, new [] { R.id }}, // Mail merge, not required
+ { W.subDoc, new [] { R.id }}, // Sub documents, not required
+ //{ w14:contentPart, new [] { R.id }},
+ { WNE.toolbarData, new [] { R.id }},
+ };
+
+
+ // This list is used to eliminate duplicate images
+ List<ImageData> images = new List<ImageData>();
+ XDocument mainPart = output.MainDocumentPart.GetXDocument();
+ mainPart.Declaration.Standalone = "yes";
+ mainPart.Declaration.Encoding = "UTF-8";
+ mainPart.Root.ReplaceWith(
+ new XElement(W.document, NamespaceAttributes,
+ new XElement(W.body)));
+ if (sources.Count > 0)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(sources[0].WmlDocument))
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ CopyStartingParts(doc, output, images);
+ }
+
+ int sourceNum2 = 0;
+ foreach (Source source in sources)
+ {
+ if (source.InsertId != null)
+ {
+ while (true)
+ {
+#if false
+ modify AppendDocument so that it can take a part.
+ for each in main document part, header parts, footer parts
+ are there any PtOpenXml.Insert elements in any of them?
+ if so, then open and process all.
+#endif
+ bool foundInMainDocPart = false;
+ XDocument mainXDoc = output.MainDocumentPart.GetXDocument();
+ if (mainXDoc.Descendants(PtOpenXml.Insert).Any(d => (string)d.Attribute(PtOpenXml.Id) == source.InsertId))
+ foundInMainDocPart = true;
+ if (foundInMainDocPart)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(source.WmlDocument))
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+#if TestForUnsupportedDocuments
+ // throws exceptions if a document contains unsupported content
+ TestForUnsupportedDocument(doc, sources.IndexOf(source));
+#endif
+ if (foundInMainDocPart)
+ {
+ if (source.KeepSections && source.DiscardHeadersAndFootersInKeptSections)
+ RemoveHeadersAndFootersFromSections(doc);
+ else if (source.KeepSections)
+ ProcessSectionsForLinkToPreviousHeadersAndFooters(doc);
+
+ List<XElement> contents = doc.MainDocumentPart.GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Skip(source.Start)
+ .Take(source.Count)
+ .ToList();
+ try
+ {
+ AppendDocument(doc, output, contents, source.KeepSections, source.InsertId, images);
+ }
+ catch (DocumentBuilderInternalException dbie)
+ {
+ if (dbie.Message.Contains("{0}"))
+ throw new DocumentBuilderException(string.Format(dbie.Message, sourceNum2));
+ else
+ throw dbie;
+ }
+ }
+ }
+ }
+ else
+ break;
+ }
+ }
+ else
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(source.WmlDocument))
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+#if TestForUnsupportedDocuments
+ // throws exceptions if a document contains unsupported content
+ TestForUnsupportedDocument(doc, sources.IndexOf(source));
+#endif
+ if (source.KeepSections && source.DiscardHeadersAndFootersInKeptSections)
+ RemoveHeadersAndFootersFromSections(doc);
+ else if (source.KeepSections)
+ ProcessSectionsForLinkToPreviousHeadersAndFooters(doc);
+
+ List<XElement> contents = doc.MainDocumentPart.GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Skip(source.Start)
+ .Take(source.Count)
+ .ToList();
+ try
+ {
+ AppendDocument(doc, output, contents, source.KeepSections, null, images);
+ }
+ catch (DocumentBuilderInternalException dbie)
+ {
+ if (dbie.Message.Contains("{0}"))
+ throw new DocumentBuilderException(string.Format(dbie.Message, sourceNum2));
+ else
+ throw dbie;
+ }
+ }
+ }
+ ++sourceNum2;
+ }
+ if (!sources.Any(s => s.KeepSections))
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(sources[0].WmlDocument))
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ var sectPr = doc.MainDocumentPart.GetXDocument().Root.Element(W.body)
+ .Elements().Last();
+ if (sectPr.Name == W.sectPr)
+ {
+ AddSectionAndDependencies(doc, output, sectPr, images);
+ output.MainDocumentPart.GetXDocument().Root.Element(W.body).Add(sectPr);
+ }
+ }
+ }
+ else
+ {
+ FixUpSectionProperties(output);
+
+ // Any sectPr elements that do not have headers and footers should take their headers and footers from the *next* section,
+ // i.e. from the running section.
+ var mxd = output.MainDocumentPart.GetXDocument();
+ var sections = mxd.Descendants(W.sectPr).Reverse().ToList();
+
+ CachedHeaderFooter[] cachedHeaderFooter = new[]
+ {
+ new CachedHeaderFooter() { Ref = W.headerReference, Type = "first" },
+ new CachedHeaderFooter() { Ref = W.headerReference, Type = "even" },
+ new CachedHeaderFooter() { Ref = W.headerReference, Type = "default" },
+ new CachedHeaderFooter() { Ref = W.footerReference, Type = "first" },
+ new CachedHeaderFooter() { Ref = W.footerReference, Type = "even" },
+ new CachedHeaderFooter() { Ref = W.footerReference, Type = "default" },
+ };
+
+ bool firstSection = true;
+ foreach (var sect in sections)
+ {
+ if (firstSection)
+ {
+ foreach (var hf in cachedHeaderFooter)
+ {
+ var referenceElement = sect.Elements(hf.Ref).FirstOrDefault(z => (string)z.Attribute(W.type) == hf.Type);
+ if (referenceElement != null)
+ hf.CachedPartRid = (string)referenceElement.Attribute(R.id);
+ }
+ firstSection = false;
+ continue;
+ }
+ else
+ {
+ CopyOrCacheHeaderOrFooter(output, cachedHeaderFooter, sect, W.headerReference, "first");
+ CopyOrCacheHeaderOrFooter(output, cachedHeaderFooter, sect, W.headerReference, "even");
+ CopyOrCacheHeaderOrFooter(output, cachedHeaderFooter, sect, W.headerReference, "default");
+ CopyOrCacheHeaderOrFooter(output, cachedHeaderFooter, sect, W.footerReference, "first");
+ CopyOrCacheHeaderOrFooter(output, cachedHeaderFooter, sect, W.footerReference, "even");
+ CopyOrCacheHeaderOrFooter(output, cachedHeaderFooter, sect, W.footerReference, "default");
+ }
+
+ }
+ }
+
+ // Now can process PtOpenXml:Insert elements in headers / footers
+ int sourceNum = 0;
+ foreach (Source source in sources)
+ {
+ if (source.InsertId != null)
+ {
+ while (true)
+ {
+#if false
+ this uses an overload of AppendDocument that takes a part.
+ for each in main document part, header parts, footer parts
+ are there any PtOpenXml.Insert elements in any of them?
+ if so, then open and process all.
+#endif
+ bool foundInHeadersFooters = false;
+ if (output.MainDocumentPart.HeaderParts.Any(hp =>
+ {
+ var hpXDoc = hp.GetXDocument();
+ return hpXDoc.Descendants(PtOpenXml.Insert).Any(d => (string)d.Attribute(PtOpenXml.Id) == source.InsertId);
+ }))
+ foundInHeadersFooters = true;
+ if (output.MainDocumentPart.FooterParts.Any(fp =>
+ {
+ var hpXDoc = fp.GetXDocument();
+ return hpXDoc.Descendants(PtOpenXml.Insert).Any(d => (string)d.Attribute(PtOpenXml.Id) == source.InsertId);
+ }))
+ foundInHeadersFooters = true;
+
+ if (foundInHeadersFooters)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(source.WmlDocument))
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+#if TestForUnsupportedDocuments
+ // throws exceptions if a document contains unsupported content
+ TestForUnsupportedDocument(doc, sources.IndexOf(source));
+#endif
+ var partList = output.MainDocumentPart.HeaderParts.Cast<OpenXmlPart>().Concat(output.MainDocumentPart.FooterParts.Cast<OpenXmlPart>()).ToList();
+ foreach (var part in partList)
+ {
+ var partXDoc = part.GetXDocument();
+ if (!partXDoc.Descendants(PtOpenXml.Insert).Any(d => (string)d.Attribute(PtOpenXml.Id) == source.InsertId))
+ continue;
+ List<XElement> contents = doc.MainDocumentPart.GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Skip(source.Start)
+ .Take(source.Count)
+ .ToList();
+ try
+ {
+ AppendDocument(doc, output, part, contents, source.KeepSections, source.InsertId, images);
+ }
+ catch (DocumentBuilderInternalException dbie)
+ {
+ if (dbie.Message.Contains("{0}"))
+ throw new DocumentBuilderException(string.Format(dbie.Message, sourceNum));
+ else
+ throw dbie;
+ }
+ }
+ }
+ }
+ else
+ break;
+ }
+ }
+ ++sourceNum;
+ }
+
+ AdjustDocPrIds(output);
+ }
+
+ foreach (var part in output.GetAllParts())
+ if (part.Annotation<XDocument>() != null)
+ part.PutXDocument();
+ }
+
+ private static void RemoveHeadersAndFootersFromSections(WordprocessingDocument doc)
+ {
+ var mdXDoc = doc.MainDocumentPart.GetXDocument();
+ var sections = mdXDoc.Descendants(W.sectPr).ToList();
+ foreach (var sect in sections)
+ {
+ sect.Elements(W.headerReference).Remove();
+ sect.Elements(W.footerReference).Remove();
+ }
+ doc.MainDocumentPart.PutXDocument();
+ }
+
+ private class CachedHeaderFooter
+ {
+ public XName Ref;
+ public string Type;
+ public string CachedPartRid;
+ };
+
+ private static void ProcessSectionsForLinkToPreviousHeadersAndFooters(WordprocessingDocument doc)
+ {
+ CachedHeaderFooter[] cachedHeaderFooter = new[]
+ {
+ new CachedHeaderFooter() { Ref = W.headerReference, Type = "first" },
+ new CachedHeaderFooter() { Ref = W.headerReference, Type = "even" },
+ new CachedHeaderFooter() { Ref = W.headerReference, Type = "default" },
+ new CachedHeaderFooter() { Ref = W.footerReference, Type = "first" },
+ new CachedHeaderFooter() { Ref = W.footerReference, Type = "even" },
+ new CachedHeaderFooter() { Ref = W.footerReference, Type = "default" },
+ };
+
+ var mdXDoc = doc.MainDocumentPart.GetXDocument();
+ var sections = mdXDoc.Descendants(W.sectPr).ToList();
+ var firstSection = true;
+ foreach (var sect in sections)
+ {
+ if (firstSection)
+ {
+ var headerFirst = FindReference(sect, W.headerReference, "first");
+ var headerDefault = FindReference(sect, W.headerReference, "default");
+ var headerEven = FindReference(sect, W.headerReference, "even");
+ var footerFirst = FindReference(sect, W.footerReference, "first");
+ var footerDefault = FindReference(sect, W.footerReference, "default");
+ var footerEven = FindReference(sect, W.footerReference, "even");
+
+ if (headerEven == null)
+ {
+ if (headerDefault != null)
+ AddReferenceToExistingHeaderOrFooter(doc.MainDocumentPart, sect, (string)headerDefault.Attribute(R.id), W.headerReference, "even");
+ else
+ InitEmptyHeaderOrFooter(doc.MainDocumentPart, sect, W.headerReference, "even");
+ }
+
+ if (headerFirst == null)
+ {
+ if (headerDefault != null)
+ AddReferenceToExistingHeaderOrFooter(doc.MainDocumentPart, sect, (string)headerDefault.Attribute(R.id), W.headerReference, "first");
+ else
+ InitEmptyHeaderOrFooter(doc.MainDocumentPart, sect, W.headerReference, "first");
+ }
+
+ if (footerEven == null)
+ {
+ if (footerDefault != null)
+ AddReferenceToExistingHeaderOrFooter(doc.MainDocumentPart, sect, (string)footerDefault.Attribute(R.id), W.footerReference, "even");
+ else
+ InitEmptyHeaderOrFooter(doc.MainDocumentPart, sect, W.footerReference, "even");
+ }
+
+ if (footerFirst == null)
+ {
+ if (footerDefault != null)
+ AddReferenceToExistingHeaderOrFooter(doc.MainDocumentPart, sect, (string)footerDefault.Attribute(R.id), W.footerReference, "first");
+ else
+ InitEmptyHeaderOrFooter(doc.MainDocumentPart, sect, W.footerReference, "first");
+ }
+
+ foreach (var hf in cachedHeaderFooter)
+ {
+ if (sect.Elements(hf.Ref).FirstOrDefault(z => (string)z.Attribute(W.type) == hf.Type) == null)
+ InitEmptyHeaderOrFooter(doc.MainDocumentPart, sect, hf.Ref, hf.Type);
+ var reference = sect.Elements(hf.Ref).FirstOrDefault(z => (string)z.Attribute(W.type) == hf.Type);
+ if (reference == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ hf.CachedPartRid = (string)reference.Attribute(R.id);
+ }
+ firstSection = false;
+ continue;
+ }
+ else
+ {
+ CopyOrCacheHeaderOrFooter(doc, cachedHeaderFooter, sect, W.headerReference, "first");
+ CopyOrCacheHeaderOrFooter(doc, cachedHeaderFooter, sect, W.headerReference, "even");
+ CopyOrCacheHeaderOrFooter(doc, cachedHeaderFooter, sect, W.headerReference, "default");
+ CopyOrCacheHeaderOrFooter(doc, cachedHeaderFooter, sect, W.footerReference, "first");
+ CopyOrCacheHeaderOrFooter(doc, cachedHeaderFooter, sect, W.footerReference, "even");
+ CopyOrCacheHeaderOrFooter(doc, cachedHeaderFooter, sect, W.footerReference, "default");
+ }
+ }
+ doc.MainDocumentPart.PutXDocument();
+ }
+
+ private static void CopyOrCacheHeaderOrFooter(WordprocessingDocument doc, CachedHeaderFooter[] cachedHeaderFooter, XElement sect, XName referenceXName, string type)
+ {
+ var referenceElement = FindReference(sect, referenceXName, type);
+ if (referenceElement == null)
+ {
+ var cachedPartRid = cachedHeaderFooter.FirstOrDefault(z => z.Ref == referenceXName && z.Type == type).CachedPartRid;
+ AddReferenceToExistingHeaderOrFooter(doc.MainDocumentPart, sect, cachedPartRid, referenceXName, type);
+ }
+ else
+ {
+ var cachedPart = cachedHeaderFooter.FirstOrDefault(z => z.Ref == referenceXName && z.Type == type);
+ cachedPart.CachedPartRid = (string)referenceElement.Attribute(R.id);
+ }
+ }
+
+ private static XElement FindReference(XElement sect, XName reference, string type)
+ {
+ return sect.Elements(reference).FirstOrDefault(z =>
+ {
+ return (string)z.Attribute(W.type) == type;
+ });
+ }
+
+ private static void AddReferenceToExistingHeaderOrFooter(MainDocumentPart mainDocPart, XElement sect, string rId, XName reference, string toType)
+ {
+ if (reference == W.headerReference)
+ {
+ var referenceToAdd = new XElement(W.headerReference,
+ new XAttribute(W.type, toType),
+ new XAttribute(R.id, rId));
+ sect.AddFirst(referenceToAdd);
+ }
+ else
+ {
+ var referenceToAdd = new XElement(W.footerReference,
+ new XAttribute(W.type, toType),
+ new XAttribute(R.id, rId));
+ sect.AddFirst(referenceToAdd);
+ }
+ }
+
+ private static void InitEmptyHeaderOrFooter(MainDocumentPart mainDocPart, XElement sect, XName referenceXName, string toType)
+ {
+ XDocument xDoc = null;
+ if (referenceXName == W.headerReference)
+ {
+ xDoc = XDocument.Parse(
+ @"<?xml version='1.0' encoding='utf-8' standalone='yes'?>
+ <w:hdr xmlns:wpc='http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas'
+ xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
+ xmlns:o='urn:schemas-microsoft-com:office:office'
+ xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships'
+ xmlns:m='http://schemas.openxmlformats.org/officeDocument/2006/math'
+ xmlns:v='urn:schemas-microsoft-com:vml'
+ xmlns:wp14='http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing'
+ xmlns:wp='http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
+ xmlns:w10='urn:schemas-microsoft-com:office:word'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'
+ xmlns:w14='http://schemas.microsoft.com/office/word/2010/wordml'
+ xmlns:w15='http://schemas.microsoft.com/office/word/2012/wordml'
+ xmlns:wpg='http://schemas.microsoft.com/office/word/2010/wordprocessingGroup'
+ xmlns:wpi='http://schemas.microsoft.com/office/word/2010/wordprocessingInk'
+ xmlns:wne='http://schemas.microsoft.com/office/word/2006/wordml'
+ xmlns:wps='http://schemas.microsoft.com/office/word/2010/wordprocessingShape'
+ mc:Ignorable='w14 w15 wp14'>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val='Header' />
+ </w:pPr>
+ <w:r>
+ <w:t></w:t>
+ </w:r>
+ </w:p>
+ </w:hdr>");
+ var newHeaderPart = mainDocPart.AddNewPart<HeaderPart>();
+ newHeaderPart.PutXDocument(xDoc);
+ var referenceToAdd = new XElement(W.headerReference,
+ new XAttribute(W.type, toType),
+ new XAttribute(R.id, mainDocPart.GetIdOfPart(newHeaderPart)));
+ sect.AddFirst(referenceToAdd);
+ }
+ else
+ {
+ xDoc = XDocument.Parse(@"<?xml version='1.0' encoding='utf-8' standalone='yes'?>
+ <w:ftr xmlns:wpc='http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas'
+ xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
+ xmlns:o='urn:schemas-microsoft-com:office:office'
+ xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships'
+ xmlns:m='http://schemas.openxmlformats.org/officeDocument/2006/math'
+ xmlns:v='urn:schemas-microsoft-com:vml'
+ xmlns:wp14='http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing'
+ xmlns:wp='http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
+ xmlns:w10='urn:schemas-microsoft-com:office:word'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'
+ xmlns:w14='http://schemas.microsoft.com/office/word/2010/wordml'
+ xmlns:w15='http://schemas.microsoft.com/office/word/2012/wordml'
+ xmlns:wpg='http://schemas.microsoft.com/office/word/2010/wordprocessingGroup'
+ xmlns:wpi='http://schemas.microsoft.com/office/word/2010/wordprocessingInk'
+ xmlns:wne='http://schemas.microsoft.com/office/word/2006/wordml'
+ xmlns:wps='http://schemas.microsoft.com/office/word/2010/wordprocessingShape'
+ mc:Ignorable='w14 w15 wp14'>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val='Footer' />
+ </w:pPr>
+ <w:r>
+ <w:t></w:t>
+ </w:r>
+ </w:p>
+ </w:ftr>");
+ var newFooterPart = mainDocPart.AddNewPart<FooterPart>();
+ newFooterPart.PutXDocument(xDoc);
+ var referenceToAdd = new XElement(W.footerReference,
+ new XAttribute(W.type, toType),
+ new XAttribute(R.id, mainDocPart.GetIdOfPart(newFooterPart)));
+ sect.AddFirst(referenceToAdd);
+ }
+ }
+
+ private static void TestPartForUnsupportedContent(OpenXmlPart part, int sourceNumber)
+ {
+ XNamespace[] obsoleteNamespaces = new[]
+ {
+ XNamespace.Get("http://schemas.microsoft.com/office/word/2007/5/30/wordml"),
+ XNamespace.Get("http://schemas.microsoft.com/office/word/2008/9/16/wordprocessingDrawing"),
+ XNamespace.Get("http://schemas.microsoft.com/office/word/2009/2/wordml"),
+ };
+ XDocument xDoc = part.GetXDocument();
+ XElement invalidElement = xDoc.Descendants()
+ .FirstOrDefault(d =>
+ {
+ bool b = d.Name == W.subDoc ||
+ d.Name == W.control ||
+ d.Name == W.altChunk ||
+ d.Name.LocalName == "contentPart" ||
+ obsoleteNamespaces.Contains(d.Name.Namespace);
+ bool b2 = b ||
+ d.Attributes().Any(a => obsoleteNamespaces.Contains(a.Name.Namespace));
+ return b2;
+ });
+ if (invalidElement != null)
+ {
+ if (invalidElement.Name == W.subDoc)
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains sub document",
+ sourceNumber));
+ if (invalidElement.Name == W.control)
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains ActiveX controls",
+ sourceNumber));
+ if (invalidElement.Name == W.altChunk)
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains altChunk",
+ sourceNumber));
+ if (invalidElement.Name.LocalName == "contentPart")
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains contentPart content",
+ sourceNumber));
+ if (obsoleteNamespaces.Contains(invalidElement.Name.Namespace) ||
+ invalidElement.Attributes().Any(a => obsoleteNamespaces.Contains(a.Name.Namespace)))
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains obsolete namespace",
+ sourceNumber));
+ }
+ }
+
+ //What does not work:
+ //- sub docs
+ //- bidi text appears to work but has not been tested
+ //- languages other than en-us appear to work but have not been tested
+ //- documents with activex controls
+ //- mail merge source documents (look for dataSource in settings)
+ //- documents with ink
+ //- documents with frame sets and frames
+ private static void TestForUnsupportedDocument(WordprocessingDocument doc, int sourceNumber)
+ {
+ if ((string)doc.MainDocumentPart.GetXDocument().Root.Name.NamespaceName == "http://purl.oclc.org/ooxml/wordprocessingml/main")
+ throw new DocumentBuilderException(string.Format("Source {0} is saved in strict mode, not supported", sourceNumber));
+
+ // note: if ever want to support section changes, need to address the code that rationalizes headers and footers, propagating to sections that inherit headers/footers from prev section
+ foreach (var d in doc.MainDocumentPart.GetXDocument().Descendants())
+ {
+ if (d.Name == W.sectPrChange)
+ throw new DocumentBuilderException(string.Format("Source {0} contains section changes (w:sectPrChange), not supported", sourceNumber));
+
+ // note: if ever want to support Open-Xml-PowerTools attributes, need to make sure that all attributes are propagated in all cases
+ //if (d.Name.Namespace == PtOpenXml.ptOpenXml ||
+ // d.Name.Namespace == PtOpenXml.pt)
+ // throw new DocumentBuilderException(string.Format("Source {0} contains Open-Xml-PowerTools markup, not supported", sourceNumber));
+ //if (d.Attributes().Any(a => a.Name.Namespace == PtOpenXml.ptOpenXml || a.Name.Namespace == PtOpenXml.pt))
+ // throw new DocumentBuilderException(string.Format("Source {0} contains Open-Xml-PowerTools markup, not supported", sourceNumber));
+ }
+
+ TestPartForUnsupportedContent(doc.MainDocumentPart, sourceNumber);
+ foreach (var hdr in doc.MainDocumentPart.HeaderParts)
+ TestPartForUnsupportedContent(hdr, sourceNumber);
+ foreach (var ftr in doc.MainDocumentPart.FooterParts)
+ TestPartForUnsupportedContent(ftr, sourceNumber);
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ TestPartForUnsupportedContent(doc.MainDocumentPart.FootnotesPart, sourceNumber);
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ TestPartForUnsupportedContent(doc.MainDocumentPart.EndnotesPart, sourceNumber);
+
+ if (doc.MainDocumentPart.DocumentSettingsPart != null &&
+ doc.MainDocumentPart.DocumentSettingsPart.GetXDocument().Descendants().Any(d => d.Name == W.src ||
+ d.Name == W.recipientData || d.Name == W.mailMerge))
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains Mail Merge content",
+ sourceNumber));
+ if (doc.MainDocumentPart.WebSettingsPart != null &&
+ doc.MainDocumentPart.WebSettingsPart.GetXDocument().Descendants().Any(d => d.Name == W.frameset))
+ throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains a frameset", sourceNumber));
+ var numberingElements = doc.MainDocumentPart
+ .GetXDocument()
+ .Descendants(W.numPr)
+ .Where(n =>
+ {
+ bool zeroId = (int?)n.Attribute(W.id) == 0;
+ bool hasChildInsId = n.Elements(W.ins).Any();
+ if (zeroId || hasChildInsId)
+ return false;
+ return true;
+ })
+ .ToList();
+ if (numberingElements.Any() &&
+ doc.MainDocumentPart.NumberingDefinitionsPart == null)
+ throw new DocumentBuilderException(String.Format(
+ "Source {0} is invalid document - contains numbering markup but no numbering part", sourceNumber));
+ }
+
+ private static void FixUpSectionProperties(WordprocessingDocument newDocument)
+ {
+ XDocument mainDocumentXDoc = newDocument.MainDocumentPart.GetXDocument();
+ mainDocumentXDoc.Declaration.Standalone = "yes";
+ mainDocumentXDoc.Declaration.Encoding = "UTF-8";
+ XElement body = mainDocumentXDoc.Root.Element(W.body);
+ var sectionPropertiesToMove = body
+ .Elements()
+ .Take(body.Elements().Count() - 1)
+ .Where(e => e.Name == W.sectPr)
+ .ToList();
+ foreach (var s in sectionPropertiesToMove)
+ {
+ var p = s.SiblingsBeforeSelfReverseDocumentOrder().First();
+ if (p.Element(W.pPr) == null)
+ p.AddFirst(new XElement(W.pPr));
+ p.Element(W.pPr).Add(s);
+ }
+ foreach (var s in sectionPropertiesToMove)
+ s.Remove();
+ }
+
+ private static void AddSectionAndDependencies(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ XElement sectionMarkup, List<ImageData> images)
+ {
+ var headerReferences = sectionMarkup.Elements(W.headerReference);
+ foreach (var headerReference in headerReferences)
+ {
+ string oldRid = headerReference.Attribute(R.id).Value;
+ HeaderPart oldHeaderPart = null;
+ try
+ {
+ oldHeaderPart = (HeaderPart)sourceDocument.MainDocumentPart.GetPartById(oldRid);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ var message = string.Format("ArgumentOutOfRangeException, attempting to get header rId={0}", oldRid);
+ throw new OpenXmlPowerToolsException(message);
+ }
+ XDocument oldHeaderXDoc = oldHeaderPart.GetXDocument();
+ if (oldHeaderXDoc != null && oldHeaderXDoc.Root != null)
+ CopyNumbering(sourceDocument, newDocument, new[] { oldHeaderXDoc.Root }, images);
+ HeaderPart newHeaderPart = newDocument.MainDocumentPart.AddNewPart<HeaderPart>();
+ XDocument newHeaderXDoc = newHeaderPart.GetXDocument();
+ newHeaderXDoc.Declaration.Standalone = "yes";
+ newHeaderXDoc.Declaration.Encoding = "UTF-8";
+ newHeaderXDoc.Add(oldHeaderXDoc.Root);
+ headerReference.Attribute(R.id).Value = newDocument.MainDocumentPart.GetIdOfPart(newHeaderPart);
+ AddRelationships(oldHeaderPart, newHeaderPart, new[] { newHeaderXDoc.Root });
+ CopyRelatedPartsForContentParts(oldHeaderPart, newHeaderPart, new[] { newHeaderXDoc.Root }, images);
+ }
+
+ var footerReferences = sectionMarkup.Elements(W.footerReference);
+ foreach (var footerReference in footerReferences)
+ {
+ string oldRid = footerReference.Attribute(R.id).Value;
+ FooterPart oldFooterPart = (FooterPart)sourceDocument.MainDocumentPart.GetPartById(oldRid);
+ XDocument oldFooterXDoc = oldFooterPart.GetXDocument();
+ if (oldFooterXDoc != null && oldFooterXDoc.Root != null)
+ CopyNumbering(sourceDocument, newDocument, new[] { oldFooterXDoc.Root }, images);
+ FooterPart newFooterPart = newDocument.MainDocumentPart.AddNewPart<FooterPart>();
+ XDocument newFooterXDoc = newFooterPart.GetXDocument();
+ newFooterXDoc.Declaration.Standalone = "yes";
+ newFooterXDoc.Declaration.Encoding = "UTF-8";
+ newFooterXDoc.Add(oldFooterXDoc.Root);
+ footerReference.Attribute(R.id).Value = newDocument.MainDocumentPart.GetIdOfPart(newFooterPart);
+ AddRelationships(oldFooterPart, newFooterPart, new[] { newFooterXDoc.Root });
+ CopyRelatedPartsForContentParts(oldFooterPart, newFooterPart, new[] { newFooterXDoc.Root }, images);
+ }
+ }
+
+ private static void MergeStyles(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, XDocument fromStyles, XDocument toStyles, IEnumerable<XElement> newContent)
+ {
+#if MergeStylesWithSameNames
+ var newIds = new Dictionary<string, string>();
+#endif
+
+ foreach (XElement style in fromStyles.Root.Elements(W.style))
+ {
+ var fromId = (string)style.Attribute(W.styleId);
+ var fromName = (string)style.Elements(W.name).Attributes(W.val).FirstOrDefault();
+
+ var toStyle = toStyles
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => (string)st.Elements(W.name).Attributes(W.val).FirstOrDefault() == fromName);
+
+ if (toStyle == null)
+ {
+#if MergeStylesWithSameNames
+ var linkElement = style.Element(W.link);
+ string linkedId;
+ if (linkElement != null && newIds.TryGetValue(linkElement.Attribute(W.val).Value, out linkedId))
+ {
+ var linkedStyle = toStyles.Root.Elements(W.style)
+ .First(o => o.Attribute(W.styleId).Value == linkedId);
+ if (linkedStyle.Element(W.link) != null)
+ newIds.Add(fromId, linkedStyle.Element(W.link).Attribute(W.val).Value);
+ continue;
+ }
+
+ //string name = (string)style.Elements(W.name).Attributes(W.val).FirstOrDefault();
+ //var namedStyle = toStyles
+ // .Root
+ // .Elements(W.style)
+ // .Where(st => st.Element(W.name) != null)
+ // .FirstOrDefault(o => (string)o.Element(W.name).Attribute(W.val) == name);
+ //if (namedStyle != null)
+ //{
+ // if (! newIds.ContainsKey(fromId))
+ // newIds.Add(fromId, namedStyle.Attribute(W.styleId).Value);
+ // continue;
+ //}
+#endif
+
+ int number = 1;
+ int abstractNumber = 0;
+ XDocument oldNumbering = null;
+ XDocument newNumbering = null;
+ foreach (XElement numReference in style.Descendants(W.numPr))
+ {
+ XElement idElement = numReference.Descendants(W.numId).FirstOrDefault();
+ if (idElement != null)
+ {
+ if (oldNumbering == null)
+ {
+ if (sourceDocument.MainDocumentPart.NumberingDefinitionsPart != null)
+ oldNumbering = sourceDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ else
+ {
+ oldNumbering = new XDocument();
+ oldNumbering.Declaration = new XDeclaration("1.0", "UTF-8", "yes");
+ oldNumbering.Add(new XElement(W.numbering, NamespaceAttributes));
+ }
+ }
+ if (newNumbering == null)
+ {
+ if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null)
+ {
+ newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ newNumbering.Declaration.Standalone = "yes";
+ newNumbering.Declaration.Encoding = "UTF-8";
+ var numIds = newNumbering
+ .Root
+ .Elements(W.num)
+ .Select(f => (int)f.Attribute(W.numId));
+ if (numIds.Any())
+ number = numIds.Max() + 1;
+ numIds = newNumbering
+ .Root
+ .Elements(W.abstractNum)
+ .Select(f => (int)f.Attribute(W.abstractNumId));
+ if (numIds.Any())
+ abstractNumber = numIds.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>();
+ newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ newNumbering.Declaration.Standalone = "yes";
+ newNumbering.Declaration.Encoding = "UTF-8";
+ newNumbering.Add(new XElement(W.numbering, NamespaceAttributes));
+ }
+ }
+ string numId = idElement.Attribute(W.val).Value;
+ if (numId != "0")
+ {
+ XElement element = oldNumbering
+ .Descendants()
+ .Elements(W.num)
+ .Where(p => ((string)p.Attribute(W.numId)) == numId)
+ .FirstOrDefault();
+
+ // Copy abstract numbering element, if necessary (use matching NSID)
+ string abstractNumId = string.Empty;
+ if (element != null)
+ {
+ abstractNumId = element
+ .Elements(W.abstractNumId)
+ .First()
+ .Attribute(W.val)
+ .Value;
+
+ XElement abstractElement = oldNumbering
+ .Descendants()
+ .Elements(W.abstractNum)
+ .Where(p => ((string)p.Attribute(W.abstractNumId)) == abstractNumId)
+ .FirstOrDefault();
+ string abstractNSID = string.Empty;
+ if (abstractElement != null)
+ {
+ XElement nsidElement = abstractElement
+ .Element(W.nsid);
+ abstractNSID = null;
+ if (nsidElement != null)
+ abstractNSID = (string)nsidElement
+ .Attribute(W.val);
+
+ XElement newAbstractElement = newNumbering
+ .Descendants()
+ .Elements(W.abstractNum)
+ .Where(e => e.Annotation<FromPreviousSourceSemaphore>() == null)
+ .Where(p =>
+ {
+ var thisNsidElement = p.Element(W.nsid);
+ if (thisNsidElement == null)
+ return false;
+ return (string)thisNsidElement.Attribute(W.val) == abstractNSID;
+ })
+ .FirstOrDefault();
+ if (newAbstractElement == null)
+ {
+ newAbstractElement = new XElement(abstractElement);
+ newAbstractElement.Attribute(W.abstractNumId).Value = abstractNumber.ToString();
+ abstractNumber++;
+ if (newNumbering.Root.Elements(W.abstractNum).Any())
+ newNumbering.Root.Elements(W.abstractNum).Last().AddAfterSelf(newAbstractElement);
+ else
+ newNumbering.Root.Add(newAbstractElement);
+
+ foreach (XElement pictId in newAbstractElement.Descendants(W.lvlPicBulletId))
+ {
+ string bulletId = (string)pictId.Attribute(W.val);
+ XElement numPicBullet = oldNumbering
+ .Descendants(W.numPicBullet)
+ .FirstOrDefault(d => (string)d.Attribute(W.numPicBulletId) == bulletId);
+ int maxNumPicBulletId = new int[] { -1 }.Concat(
+ newNumbering.Descendants(W.numPicBullet)
+ .Attributes(W.numPicBulletId)
+ .Select(a => (int)a))
+ .Max() + 1;
+ XElement newNumPicBullet = new XElement(numPicBullet);
+ newNumPicBullet.Attribute(W.numPicBulletId).Value = maxNumPicBulletId.ToString();
+ pictId.Attribute(W.val).Value = maxNumPicBulletId.ToString();
+ newNumbering.Root.AddFirst(newNumPicBullet);
+ }
+ }
+ string newAbstractId = newAbstractElement.Attribute(W.abstractNumId).Value;
+
+ // Copy numbering element, if necessary (use matching element with no overrides)
+ XElement newElement = null;
+ if (!element.Elements(W.lvlOverride).Any())
+ newElement = newNumbering
+ .Descendants()
+ .Elements(W.num)
+ .Where(p => !p.Elements(W.lvlOverride).Any() &&
+ ((string)p.Elements(W.abstractNumId).First().Attribute(W.val)) == newAbstractId)
+ .FirstOrDefault();
+ if (newElement == null)
+ {
+ newElement = new XElement(element);
+ newElement
+ .Elements(W.abstractNumId)
+ .First()
+ .Attribute(W.val).Value = newAbstractId;
+ newElement.Attribute(W.numId).Value = number.ToString();
+ number++;
+ newNumbering.Root.Add(newElement);
+ }
+ idElement.Attribute(W.val).Value = newElement.Attribute(W.numId).Value;
+ }
+ }
+ }
+ }
+ }
+
+ var newStyle = new XElement(style);
+ // get rid of anything not in the w: namespace
+ newStyle.Descendants().Where(d => d.Name.NamespaceName != W.w).Remove();
+ newStyle.Descendants().Attributes().Where(d => d.Name.NamespaceName != W.w).Remove();
+ toStyles.Root.Add(newStyle);
+ }
+ else
+ {
+ var toId = (string)toStyle.Attribute(W.styleId);
+ if (fromId != toId)
+ {
+ if (! newIds.ContainsKey(fromId))
+ newIds.Add(fromId, toId);
+ }
+ }
+ }
+
+#if MergeStylesWithSameNames
+ if (newIds.Count > 0)
+ {
+ foreach (var style in toStyles
+ .Root
+ .Elements(W.style))
+ {
+ ConvertToNewId(style.Element(W.basedOn), newIds);
+ ConvertToNewId(style.Element(W.next), newIds);
+ }
+
+ foreach (var item in newContent.DescendantsAndSelf()
+ .Where(d => d.Name == W.pStyle ||
+ d.Name == W.rStyle ||
+ d.Name == W.tblStyle))
+ {
+ ConvertToNewId(item, newIds);
+ }
+
+ if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null)
+ {
+ var newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ ConvertNumberingPartToNewIds(newNumbering, newIds);
+ }
+
+ // Convert source document, since numberings will be copied over after styles.
+ if (sourceDocument.MainDocumentPart.NumberingDefinitionsPart != null)
+ {
+ var sourceNumbering = sourceDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ ConvertNumberingPartToNewIds(sourceNumbering, newIds);
+ }
+ }
+#endif
+ }
+
+#if MergeStylesWithSameNames
+ private static void ConvertToNewId(XElement element, Dictionary<string, string> newIds)
+ {
+ if (element == null)
+ return;
+
+ var valueAttribute = element.Attribute(W.val);
+ string newId;
+ if (newIds.TryGetValue(valueAttribute.Value, out newId))
+ {
+ valueAttribute.Value = newId;
+ }
+ }
+
+ private static void ConvertNumberingPartToNewIds(XDocument newNumbering, Dictionary<string, string> newIds)
+ {
+ foreach (var abstractNum in newNumbering
+ .Root
+ .Elements(W.abstractNum))
+ {
+ ConvertToNewId(abstractNum.Element(W.styleLink), newIds);
+ ConvertToNewId(abstractNum.Element(W.numStyleLink), newIds);
+ }
+
+ foreach (var item in newNumbering
+ .Descendants()
+ .Where(d => d.Name == W.pStyle ||
+ d.Name == W.rStyle ||
+ d.Name == W.tblStyle))
+ {
+ ConvertToNewId(item, newIds);
+ }
+ }
+#endif
+
+ private static void MergeFontTables(XDocument fromFontTable, XDocument toFontTable)
+ {
+ foreach (XElement font in fromFontTable.Root.Elements(W.font))
+ {
+ string name = font.Attribute(W.name).Value;
+ if (toFontTable
+ .Root
+ .Elements(W.font)
+ .Where(o => o.Attribute(W.name).Value == name)
+ .Count() == 0)
+ toFontTable.Root.Add(new XElement(font));
+ }
+ }
+
+ private static void CopyStylesAndFonts(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ IEnumerable<XElement> newContent)
+ {
+ // Copy all styles to the new document
+ if (sourceDocument.MainDocumentPart.StyleDefinitionsPart != null)
+ {
+ XDocument oldStyles = sourceDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ if (newDocument.MainDocumentPart.StyleDefinitionsPart == null)
+ {
+ newDocument.MainDocumentPart.AddNewPart<StyleDefinitionsPart>();
+ XDocument newStyles = newDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ newStyles.Declaration.Standalone = "yes";
+ newStyles.Declaration.Encoding = "UTF-8";
+ newStyles.Add(oldStyles.Root);
+ }
+ else
+ {
+ XDocument newStyles = newDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ MergeStyles(sourceDocument, newDocument, oldStyles, newStyles, newContent);
+ }
+ }
+
+ // Copy all styles with effects to the new document
+ if (sourceDocument.MainDocumentPart.StylesWithEffectsPart != null)
+ {
+ XDocument oldStyles = sourceDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument();
+ if (newDocument.MainDocumentPart.StylesWithEffectsPart == null)
+ {
+ newDocument.MainDocumentPart.AddNewPart<StylesWithEffectsPart>();
+ XDocument newStyles = newDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument();
+ newStyles.Declaration.Standalone = "yes";
+ newStyles.Declaration.Encoding = "UTF-8";
+ newStyles.Add(oldStyles.Root);
+ }
+ else
+ {
+ XDocument newStyles = newDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument();
+ MergeStyles(sourceDocument, newDocument, oldStyles, newStyles, newContent);
+ }
+ }
+
+ // Copy fontTable to the new document
+ if (sourceDocument.MainDocumentPart.FontTablePart != null)
+ {
+ XDocument oldFontTable = sourceDocument.MainDocumentPart.FontTablePart.GetXDocument();
+ if (newDocument.MainDocumentPart.FontTablePart == null)
+ {
+ newDocument.MainDocumentPart.AddNewPart<FontTablePart>();
+ XDocument newFontTable = newDocument.MainDocumentPart.FontTablePart.GetXDocument();
+ newFontTable.Declaration.Standalone = "yes";
+ newFontTable.Declaration.Encoding = "UTF-8";
+ newFontTable.Add(oldFontTable.Root);
+ }
+ else
+ {
+ XDocument newFontTable = newDocument.MainDocumentPart.FontTablePart.GetXDocument();
+ MergeFontTables(oldFontTable, newFontTable);
+ }
+ }
+ }
+
+ private static void CopyComments(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ IEnumerable<XElement> newContent, List<ImageData> images)
+ {
+ Dictionary<int, int> commentIdMap = new Dictionary<int, int>();
+ int number = 0;
+ XDocument oldComments = null;
+ XDocument newComments = null;
+ foreach (XElement comment in newContent.DescendantsAndSelf(W.commentReference))
+ {
+ if (oldComments == null)
+ oldComments = sourceDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument();
+ if (newComments == null)
+ {
+ if (newDocument.MainDocumentPart.WordprocessingCommentsPart != null)
+ {
+ newComments = newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument();
+ newComments.Declaration.Standalone = "yes";
+ newComments.Declaration.Encoding = "UTF-8";
+ var ids = newComments.Root.Elements(W.comment).Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ number = ids.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<WordprocessingCommentsPart>();
+ newComments = newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument();
+ newComments.Declaration.Standalone = "yes";
+ newComments.Declaration.Encoding = "UTF-8";
+ newComments.Add(new XElement(W.comments, NamespaceAttributes));
+ }
+ }
+ int id = (int)comment.Attribute(W.id);
+ XElement element = oldComments
+ .Descendants()
+ .Elements(W.comment)
+ .Where(p => ((int)p.Attribute(W.id)) == id)
+ .First();
+ XElement newElement = new XElement(element);
+ newElement.Attribute(W.id).Value = number.ToString();
+ newComments.Root.Add(newElement);
+ if (! commentIdMap.ContainsKey(id))
+ commentIdMap.Add(id, number);
+ number++;
+ }
+ foreach (var item in newContent.DescendantsAndSelf()
+ .Where(d => d.Name == W.commentReference ||
+ d.Name == W.commentRangeStart ||
+ d.Name == W.commentRangeEnd)
+ .ToList())
+ {
+ if (commentIdMap.ContainsKey((int)item.Attribute(W.id)))
+ item.Attribute(W.id).Value = commentIdMap[(int)item.Attribute(W.id)].ToString();
+ }
+ if (sourceDocument.MainDocumentPart.WordprocessingCommentsPart != null &&
+ newDocument.MainDocumentPart.WordprocessingCommentsPart != null)
+ {
+ AddRelationships(sourceDocument.MainDocumentPart.WordprocessingCommentsPart,
+ newDocument.MainDocumentPart.WordprocessingCommentsPart,
+ new[] { newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.WordprocessingCommentsPart,
+ newDocument.MainDocumentPart.WordprocessingCommentsPart,
+ new[] { newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument().Root },
+ images);
+ }
+ }
+
+ private static void AdjustUniqueIds(WordprocessingDocument sourceDocument,
+ WordprocessingDocument newDocument, IEnumerable<XElement> newContent)
+ {
+ // adjust bookmark unique ids
+ int maxId = 0;
+ if (newDocument.MainDocumentPart.GetXDocument().Descendants(W.bookmarkStart).Any())
+ maxId = newDocument.MainDocumentPart.GetXDocument().Descendants(W.bookmarkStart)
+ .Select(d => (int)d.Attribute(W.id)).Max();
+ Dictionary<int, int> bookmarkIdMap = new Dictionary<int, int>();
+ foreach (var item in newContent.DescendantsAndSelf().Where(bm => bm.Name == W.bookmarkStart ||
+ bm.Name == W.bookmarkEnd))
+ {
+ int id = (int)item.Attribute(W.id);
+ if (!bookmarkIdMap.ContainsKey(id))
+ bookmarkIdMap.Add(id, ++maxId);
+ }
+ foreach (var bookmarkElement in newContent.DescendantsAndSelf().Where(e => e.Name == W.bookmarkStart ||
+ e.Name == W.bookmarkEnd))
+ bookmarkElement.Attribute(W.id).Value = bookmarkIdMap[(int)bookmarkElement.Attribute(W.id)].ToString();
+
+ // adjust shape unique ids
+ // This doesn't work because OLEObjects refer to shapes by ID.
+ // Punting on this, because sooner or later, this will be a non-issue.
+ //foreach (var item in newContent.DescendantsAndSelf(VML.shape))
+ //{
+ // Guid g = Guid.NewGuid();
+ // string s = "R" + g.ToString().Replace("-", "");
+ // item.Attribute(NoNamespace.id).Value = s;
+ //}
+ }
+
+ private static void AdjustDocPrIds(WordprocessingDocument newDocument)
+ {
+ int docPrId = 0;
+ foreach (var item in newDocument.MainDocumentPart.GetXDocument().Descendants(WP.docPr))
+ item.Attribute(NoNamespace.id).Value = (++docPrId).ToString();
+ foreach (var header in newDocument.MainDocumentPart.HeaderParts)
+ foreach (var item in header.GetXDocument().Descendants(WP.docPr))
+ item.Attribute(NoNamespace.id).Value = (++docPrId).ToString();
+ foreach (var footer in newDocument.MainDocumentPart.FooterParts)
+ foreach (var item in footer.GetXDocument().Descendants(WP.docPr))
+ item.Attribute(NoNamespace.id).Value = (++docPrId).ToString();
+ if (newDocument.MainDocumentPart.FootnotesPart != null)
+ foreach (var item in newDocument.MainDocumentPart.FootnotesPart.GetXDocument().Descendants(WP.docPr))
+ item.Attribute(NoNamespace.id).Value = (++docPrId).ToString();
+ if (newDocument.MainDocumentPart.EndnotesPart != null)
+ foreach (var item in newDocument.MainDocumentPart.EndnotesPart.GetXDocument().Descendants(WP.docPr))
+ item.Attribute(NoNamespace.id).Value = (++docPrId).ToString();
+ }
+
+ // This probably doesn't need to be done, except that the Open XML SDK will not validate
+ // documents that contain the o:gfxdata attribute.
+ private static void RemoveGfxdata(IEnumerable<XElement> newContent)
+ {
+ newContent.DescendantsAndSelf().Attributes(O.gfxdata).Remove();
+ }
+
+ private static object InsertTransform(XNode node, List<XElement> newContent)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Annotation<ReplaceSemaphore>() != null)
+ return newContent;
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => InsertTransform(n, newContent)));
+ }
+ return node;
+ }
+
+ private class ReplaceSemaphore { }
+
+ // Rules for sections
+ // - if KeepSections for all documents in the source collection are false, then it takes the section
+ // from the first document.
+ // - if you specify true for any document, and if the last section is part of the specified content,
+ // then that section is copied. If any paragraph in the content has a section, then that section
+ // is copied.
+ // - if you specify true for any document, and there are no sections for any paragraphs, then no
+ // sections are copied.
+ private static void AppendDocument(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ List<XElement> newContent, bool keepSection, string insertId, List<ImageData> images)
+ {
+ FixRanges(sourceDocument.MainDocumentPart.GetXDocument(), newContent);
+ AddRelationships(sourceDocument.MainDocumentPart, newDocument.MainDocumentPart, newContent);
+ CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart, newDocument.MainDocumentPart,
+ newContent, images);
+
+ // Append contents
+ XDocument newMainXDoc = newDocument.MainDocumentPart.GetXDocument();
+ newMainXDoc.Declaration.Standalone = "yes";
+ newMainXDoc.Declaration.Encoding = "UTF-8";
+ if (keepSection == false)
+ {
+ List<XElement> adjustedContents = newContent.Where(e => e.Name != W.sectPr).ToList();
+ adjustedContents.DescendantsAndSelf(W.sectPr).Remove();
+ newContent = adjustedContents;
+ }
+ var listOfSectionProps = newContent.DescendantsAndSelf(W.sectPr).ToList();
+ foreach (var sectPr in listOfSectionProps)
+ AddSectionAndDependencies(sourceDocument, newDocument, sectPr, images);
+ CopyStylesAndFonts(sourceDocument, newDocument, newContent);
+ CopyNumbering(sourceDocument, newDocument, newContent, images);
+ CopyComments(sourceDocument, newDocument, newContent, images);
+ CopyFootnotes(sourceDocument, newDocument, newContent, images);
+ CopyEndnotes(sourceDocument, newDocument, newContent, images);
+ AdjustUniqueIds(sourceDocument, newDocument, newContent);
+ RemoveGfxdata(newContent);
+ CopyCustomXml(sourceDocument, newDocument, newContent);
+ CopyWebExtensions(sourceDocument, newDocument);
+ if (insertId != null)
+ {
+ XElement insertElementToReplace = newMainXDoc
+ .Descendants(PtOpenXml.Insert)
+ .FirstOrDefault(i => (string)i.Attribute(PtOpenXml.Id) == insertId);
+ if (insertElementToReplace != null)
+ insertElementToReplace.AddAnnotation(new ReplaceSemaphore());
+ newMainXDoc.Element(W.document).ReplaceWith((XElement)InsertTransform(newMainXDoc.Root, newContent));
+ }
+ else
+ newMainXDoc.Root.Element(W.body).Add(newContent);
+
+ if (newMainXDoc.Descendants().Any(d =>
+ {
+ if (d.Name.Namespace == PtOpenXml.pt || d.Name.Namespace == PtOpenXml.ptOpenXml)
+ return true;
+ if (d.Attributes().Any(att => att.Name.Namespace == PtOpenXml.pt || att.Name.Namespace == PtOpenXml.ptOpenXml))
+ return true;
+ return false;
+ }))
+ {
+ var root = newMainXDoc.Root;
+ if (!root.Attributes().Any(na => na.Value == PtOpenXml.pt.NamespaceName))
+ {
+ root.Add(new XAttribute(XNamespace.Xmlns + "pt", PtOpenXml.pt.NamespaceName));
+ AddToIgnorable(root, "pt");
+ }
+ if (!root.Attributes().Any(na => na.Value == PtOpenXml.ptOpenXml.NamespaceName))
+ {
+ root.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.ptOpenXml.NamespaceName));
+ AddToIgnorable(root, "pt14");
+ }
+ }
+ }
+
+ private static void CopyWebExtensions(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument)
+ {
+ if (sourceDocument.WebExTaskpanesPart != null && newDocument.WebExTaskpanesPart == null)
+ {
+ newDocument.AddWebExTaskpanesPart();
+ newDocument.WebExTaskpanesPart.GetXDocument().Add(sourceDocument.WebExTaskpanesPart.GetXDocument().Root);
+
+ foreach (var sourceWebExtensionPart in sourceDocument.WebExTaskpanesPart.WebExtensionParts)
+ {
+ var newWebExtensionpart = newDocument.WebExTaskpanesPart.AddNewPart<WebExtensionPart>(
+ sourceDocument.WebExTaskpanesPart.GetIdOfPart(sourceWebExtensionPart));
+ newWebExtensionpart.GetXDocument().Add(sourceWebExtensionPart.GetXDocument().Root);
+ }
+ }
+ }
+
+ private static void AddToIgnorable(XElement root, string v)
+ {
+ var ignorable = root.Attribute(MC.Ignorable);
+ if (ignorable != null)
+ {
+ var val = (string)ignorable;
+ val = val + " " + v;
+ ignorable.Remove();
+ root.SetAttributeValue(MC.Ignorable, val);
+ }
+ }
+
+ /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ /// New method to support new functionality
+ private static void AppendDocument(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, OpenXmlPart part,
+ List<XElement> newContent, bool keepSection, string insertId, List<ImageData> images)
+ {
+ // Append contents
+ XDocument partXDoc = part.GetXDocument();
+ partXDoc.Declaration.Standalone = "yes";
+ partXDoc.Declaration.Encoding = "UTF-8";
+
+ FixRanges(part.GetXDocument(), newContent);
+ AddRelationships(sourceDocument.MainDocumentPart, part, newContent);
+ CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart, part,
+ newContent, images);
+
+ // never keep sections for content to be inserted into a header/footer
+ List<XElement> adjustedContents = newContent.Where(e => e.Name != W.sectPr).ToList();
+ adjustedContents.DescendantsAndSelf(W.sectPr).Remove();
+ newContent = adjustedContents;
+
+ CopyNumbering(sourceDocument, newDocument, newContent, images);
+ CopyComments(sourceDocument, newDocument, newContent, images);
+ AdjustUniqueIds(sourceDocument, newDocument, newContent);
+ RemoveGfxdata(newContent);
+
+ if (insertId == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ XElement insertElementToReplace = partXDoc
+ .Descendants(PtOpenXml.Insert)
+ .FirstOrDefault(i => (string)i.Attribute(PtOpenXml.Id) == insertId);
+ if (insertElementToReplace != null)
+ insertElementToReplace.AddAnnotation(new ReplaceSemaphore());
+ partXDoc.Elements().First().ReplaceWith((XElement)InsertTransform(partXDoc.Root, newContent));
+ }
+ /// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ private static void CopyCustomXml(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ IEnumerable<XElement> newContent)
+ {
+ List<string> itemList = new List<string>();
+ foreach (string itemId in newContent
+ .Descendants(W.dataBinding)
+ .Select(e => (string)e.Attribute(W.storeItemID)))
+ if (!itemList.Contains(itemId))
+ itemList.Add(itemId);
+ foreach (CustomXmlPart customXmlPart in sourceDocument.MainDocumentPart.CustomXmlParts)
+ {
+ OpenXmlPart propertyPart = customXmlPart
+ .Parts
+ .Select(p => p.OpenXmlPart)
+ .Where(p => p.ContentType == "application/vnd.openxmlformats-officedocument.customXmlProperties+xml")
+ .FirstOrDefault();
+ if (propertyPart != null)
+ {
+ XDocument propertyPartDoc = propertyPart.GetXDocument();
+ if (itemList.Contains(propertyPartDoc.Root.Attribute(DS.itemID).Value))
+ {
+ CustomXmlPart newPart = newDocument.MainDocumentPart.AddCustomXmlPart(customXmlPart.ContentType);
+ newPart.GetXDocument().Add(customXmlPart.GetXDocument().Root);
+ foreach (OpenXmlPart propPart in customXmlPart.Parts.Select(p => p.OpenXmlPart))
+ {
+ CustomXmlPropertiesPart newPropPart = newPart.AddNewPart<CustomXmlPropertiesPart>();
+ newPropPart.GetXDocument().Add(propPart.GetXDocument().Root);
+ }
+ }
+ }
+ }
+ }
+
+ private static Dictionary<XName, XName[]> RelationshipMarkup = null;
+
+ private static void UpdateContent(IEnumerable<XElement> newContent, XName elementToModify, string oldRid, string newRid)
+ {
+ foreach (var attributeName in RelationshipMarkup[elementToModify])
+ {
+ var elementsToUpdate = newContent
+ .Descendants(elementToModify)
+ .Where(e => (string)e.Attribute(attributeName) == oldRid);
+ foreach (var element in elementsToUpdate)
+ element.Attribute(attributeName).Value = newRid;
+ }
+ }
+
+ private static void AddRelationships(OpenXmlPart oldPart, OpenXmlPart newPart, IEnumerable<XElement> newContent)
+ {
+ var relevantElements = newContent.DescendantsAndSelf()
+ .Where(d => RelationshipMarkup.ContainsKey(d.Name) &&
+ d.Attributes().Any(a => RelationshipMarkup[d.Name].Contains(a.Name)))
+ .ToList();
+ foreach (var e in relevantElements)
+ {
+ if (e.Name == W.hyperlink)
+ {
+ string relId = (string)e.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempHyperlink = newPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempHyperlink != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldHyperlink = oldPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldHyperlink == null)
+ continue;
+ //throw new DocumentBuilderInternalException("Internal Error 0002");
+ newPart.AddHyperlinkRelationship(oldHyperlink.Uri, oldHyperlink.IsExternal, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ if (e.Name == W.attachedTemplate || e.Name == W.saveThroughXslt)
+ {
+ string relId = (string)e.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempExternalRelationship != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ throw new DocumentBuilderInternalException("Source {0} is invalid document - hyperlink contains invalid references");
+ newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ if (e.Name == A.hlinkClick || e.Name == A.hlinkHover || e.Name == A.hlinkMouseOver)
+ {
+ string relId = (string)e.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempHyperlink = newPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempHyperlink != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldHyperlink = oldPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldHyperlink == null)
+ continue;
+ newPart.AddHyperlinkRelationship(oldHyperlink.Uri, oldHyperlink.IsExternal, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ if (e.Name == VML.imagedata)
+ {
+ string relId = (string)e.Attribute(R.href);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempExternalRelationship != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ throw new DocumentBuilderInternalException("Internal Error 0006");
+ newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ if (e.Name == A.blip)
+ {
+ // <a:blip r:embed="rId6" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" />
+ string relId = (string)e.Attribute(R.link);
+ //if (relId == null)
+ // relId = (string)e.Attribute(R.embed);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempExternalRelationship != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ continue;
+ newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ }
+ }
+
+ private class FromPreviousSourceSemaphore { };
+
+ private static void CopyNumbering(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ IEnumerable<XElement> newContent, List<ImageData> images)
+ {
+ Dictionary<int, int> numIdMap = new Dictionary<int, int>();
+ int number = 1;
+ int abstractNumber = 0;
+ XDocument oldNumbering = null;
+ XDocument newNumbering = null;
+
+ foreach (XElement numReference in newContent.DescendantsAndSelf(W.numPr))
+ {
+ XElement idElement = numReference.Descendants(W.numId).FirstOrDefault();
+ if (idElement != null)
+ {
+ if (oldNumbering == null)
+ oldNumbering = sourceDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ if (newNumbering == null)
+ {
+ if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null)
+ {
+ newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ var numIds = newNumbering
+ .Root
+ .Elements(W.num)
+ .Select(f => (int)f.Attribute(W.numId));
+ if (numIds.Any())
+ number = numIds.Max() + 1;
+ numIds = newNumbering
+ .Root
+ .Elements(W.abstractNum)
+ .Select(f => (int)f.Attribute(W.abstractNumId));
+ if (numIds.Any())
+ abstractNumber = numIds.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>();
+ newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ newNumbering.Declaration.Standalone = "yes";
+ newNumbering.Declaration.Encoding = "UTF-8";
+ newNumbering.Add(new XElement(W.numbering, NamespaceAttributes));
+ }
+ }
+ int numId = (int)idElement.Attribute(W.val);
+ if (numId != 0)
+ {
+ XElement element = oldNumbering
+ .Descendants(W.num)
+ .Where(p => ((int)p.Attribute(W.numId)) == numId)
+ .FirstOrDefault();
+ if (element == null)
+ continue;
+
+ // Copy abstract numbering element, if necessary (use matching NSID)
+ int abstractNumId = (int)element
+ .Elements(W.abstractNumId)
+ .First()
+ .Attribute(W.val);
+ XElement abstractElement = oldNumbering
+ .Descendants()
+ .Elements(W.abstractNum)
+ .Where(p => ((int)p.Attribute(W.abstractNumId)) == abstractNumId)
+ .First();
+ XElement nsidElement = abstractElement
+ .Element(W.nsid);
+ string abstractNSID = null;
+ if (nsidElement != null)
+ abstractNSID = (string)nsidElement
+ .Attribute(W.val);
+ XElement newAbstractElement = newNumbering
+ .Descendants()
+ .Elements(W.abstractNum)
+ .Where(e => e.Annotation<FromPreviousSourceSemaphore>() == null)
+ .Where(p =>
+ {
+ var thisNsidElement = p.Element(W.nsid);
+ if (thisNsidElement == null)
+ return false;
+ return (string)thisNsidElement.Attribute(W.val) == abstractNSID;
+ })
+ .FirstOrDefault();
+ if (newAbstractElement == null)
+ {
+ newAbstractElement = new XElement(abstractElement);
+ newAbstractElement.Attribute(W.abstractNumId).Value = abstractNumber.ToString();
+ abstractNumber++;
+ if (newNumbering.Root.Elements(W.abstractNum).Any())
+ newNumbering.Root.Elements(W.abstractNum).Last().AddAfterSelf(newAbstractElement);
+ else
+ newNumbering.Root.Add(newAbstractElement);
+
+ foreach (XElement pictId in newAbstractElement.Descendants(W.lvlPicBulletId))
+ {
+ string bulletId = (string)pictId.Attribute(W.val);
+ XElement numPicBullet = oldNumbering
+ .Descendants(W.numPicBullet)
+ .FirstOrDefault(d => (string)d.Attribute(W.numPicBulletId) == bulletId);
+ int maxNumPicBulletId = new int[] { -1 }.Concat(
+ newNumbering.Descendants(W.numPicBullet)
+ .Attributes(W.numPicBulletId)
+ .Select(a => (int)a))
+ .Max() + 1;
+ XElement newNumPicBullet = new XElement(numPicBullet);
+ newNumPicBullet.Attribute(W.numPicBulletId).Value = maxNumPicBulletId.ToString();
+ pictId.Attribute(W.val).Value = maxNumPicBulletId.ToString();
+ newNumbering.Root.AddFirst(newNumPicBullet);
+ }
+ }
+ string newAbstractId = newAbstractElement.Attribute(W.abstractNumId).Value;
+
+ // Copy numbering element, if necessary (use matching element with no overrides)
+ XElement newElement;
+ if (numIdMap.ContainsKey(numId))
+ {
+ newElement = newNumbering
+ .Descendants()
+ .Elements(W.num)
+ .Where(e => e.Annotation<FromPreviousSourceSemaphore>() == null)
+ .Where(p => ((int)p.Attribute(W.numId)) == numIdMap[numId])
+ .First();
+ }
+ else
+ {
+ newElement = new XElement(element);
+ newElement
+ .Elements(W.abstractNumId)
+ .First()
+ .Attribute(W.val).Value = newAbstractId;
+ newElement.Attribute(W.numId).Value = number.ToString();
+ numIdMap.Add(numId, number);
+ number++;
+ newNumbering.Root.Add(newElement);
+ }
+ idElement.Attribute(W.val).Value = newElement.Attribute(W.numId).Value;
+ }
+ }
+ }
+ if (newNumbering != null)
+ {
+ foreach (var abstractNum in newNumbering.Descendants(W.abstractNum))
+ abstractNum.AddAnnotation(new FromPreviousSourceSemaphore());
+ foreach (var num in newNumbering.Descendants(W.num))
+ num.AddAnnotation(new FromPreviousSourceSemaphore());
+ }
+
+ if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null &&
+ sourceDocument.MainDocumentPart.NumberingDefinitionsPart != null)
+ {
+ AddRelationships(sourceDocument.MainDocumentPart.NumberingDefinitionsPart,
+ newDocument.MainDocumentPart.NumberingDefinitionsPart,
+ new[] { newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.NumberingDefinitionsPart,
+ newDocument.MainDocumentPart.NumberingDefinitionsPart,
+ new[] { newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument().Root }, images);
+ }
+ }
+
+ private static void CopyRelatedImage(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement imageReference, XName attributeName,
+ List<ImageData> images)
+ {
+ string relId = (string)imageReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ // First look to see if this relId has already been added to the new document.
+ // This is necessary for those parts that get processed with both old and new ids, such as the comments
+ // part. This is not necessary for parts such as the main document part, but this code won't malfunction
+ // in that case.
+ var tempPartIdPair5 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair5 != null)
+ return;
+
+ ExternalRelationship tempEr5 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr5 != null)
+ return;
+
+ var ipp2 = oldContentPart.Parts.FirstOrDefault(ipp => ipp.RelationshipId == relId);
+ if (ipp2 != null)
+ {
+ ImagePart oldPart = (ImagePart)ipp2.OpenXmlPart;
+ ImageData temp = ManageImageCopy(oldPart, newContentPart, images);
+ if (temp.ImagePart == null)
+ {
+ ImagePart newPart = null;
+ if (newContentPart is MainDocumentPart)
+ newPart = ((MainDocumentPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is HeaderPart)
+ newPart = ((HeaderPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is FooterPart)
+ newPart = ((FooterPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is EndnotesPart)
+ newPart = ((EndnotesPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is FootnotesPart)
+ newPart = ((FootnotesPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is ThemePart)
+ newPart = ((ThemePart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is WordprocessingCommentsPart)
+ newPart = ((WordprocessingCommentsPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is DocumentSettingsPart)
+ newPart = ((DocumentSettingsPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is ChartPart)
+ newPart = ((ChartPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is NumberingDefinitionsPart)
+ newPart = ((NumberingDefinitionsPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is DiagramDataPart)
+ newPart = ((DiagramDataPart)newContentPart).AddImagePart(oldPart.ContentType);
+ if (newContentPart is ChartDrawingPart)
+ newPart = ((ChartDrawingPart)newContentPart).AddImagePart(oldPart.ContentType);
+ temp.ImagePart = newPart;
+ var id = newContentPart.GetIdOfPart(newPart);
+ temp.AddContentPartRelTypeResourceIdTupple(newContentPart, newPart.RelationshipType, id);
+ imageReference.Attribute(attributeName).Value = id;
+ temp.WriteImage(newPart);
+ }
+ else
+ {
+ var refRel = newContentPart.Parts.FirstOrDefault(pip =>
+ {
+ var rel = temp.ContentPartRelTypeIdList.FirstOrDefault(cpr =>
+ {
+ var found = cpr.ContentPart == newContentPart;
+ return found;
+ });
+ return rel != null;
+ });
+ if (refRel != null)
+ {
+ imageReference.Attribute(attributeName).Value = temp.ContentPartRelTypeIdList.First(cpr =>
+ {
+ var found = cpr.ContentPart == newContentPart;
+ return found;
+ }).RelationshipId;
+ return;
+ }
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ newContentPart.CreateRelationshipToPart(temp.ImagePart, newId);
+ imageReference.Attribute(R.id).Value = newId;
+ }
+ }
+ else
+ {
+ ExternalRelationship er = oldContentPart.ExternalRelationships.FirstOrDefault(er1 => er1.Id == relId);
+ if (er != null)
+ {
+ ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri);
+ imageReference.Attribute(R.id).Value = newEr.Id;
+ }
+ throw new DocumentBuilderInternalException("Source {0} is unsupported document - contains reference to NULL image");
+ }
+ }
+
+ private static void CopyRelatedPartsForContentParts(OpenXmlPart oldContentPart, OpenXmlPart newContentPart,
+ IEnumerable<XElement> newContent, List<ImageData> images)
+ {
+ var relevantElements = newContent.DescendantsAndSelf()
+ .Where(d => d.Name == VML.imagedata || d.Name == VML.fill || d.Name == VML.stroke || d.Name == A.blip)
+ .ToList();
+ foreach (XElement imageReference in relevantElements)
+ {
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.embed, images);
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.pict, images);
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.id, images);
+ }
+
+ foreach (XElement diagramReference in newContent.DescendantsAndSelf().Where(d => d.Name == DGM.relIds || d.Name == A.relIds))
+ {
+ // dm attribute
+ string relId = diagramReference.Attribute(R.dm).Value;
+ var ipp = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (ipp != null)
+ {
+ OpenXmlPart tempPart = ipp.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr = newContentPart.ExternalRelationships.FirstOrDefault(er2 => er2.Id == relId);
+ if (tempEr != null)
+ continue;
+
+ OpenXmlPart oldPart = oldContentPart.GetPartById(relId);
+ OpenXmlPart newPart = newContentPart.AddNewPart<DiagramDataPart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.dm).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images);
+
+ // lo attribute
+ relId = diagramReference.Attribute(R.lo).Value;
+ var ipp2 = newContentPart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp2 != null)
+ {
+ OpenXmlPart tempPart = ipp2.OpenXmlPart;
+ continue;
+ }
+
+
+ ExternalRelationship tempEr4 = newContentPart.ExternalRelationships.FirstOrDefault(er3 => er3.Id == relId);
+ if (tempEr4 != null)
+ continue;
+
+ oldPart = oldContentPart.GetPartById(relId);
+ newPart = newContentPart.AddNewPart<DiagramLayoutDefinitionPart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.lo).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images);
+
+ // qs attribute
+ relId = diagramReference.Attribute(R.qs).Value;
+ var ipp5 = newContentPart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp5 != null)
+ {
+ OpenXmlPart tempPart = ipp5.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr5 = newContentPart.ExternalRelationships.FirstOrDefault(z => z.Id == relId);
+ if (tempEr5 != null)
+ continue;
+
+ oldPart = oldContentPart.GetPartById(relId);
+ newPart = newContentPart.AddNewPart<DiagramStylePart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.qs).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images);
+
+ // cs attribute
+ relId = diagramReference.Attribute(R.cs).Value;
+ var ipp6 = newContentPart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp6 != null)
+ {
+ OpenXmlPart tempPart = ipp6.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr6 = newContentPart.ExternalRelationships.FirstOrDefault(z => z.Id == relId);
+ if (tempEr6 != null)
+ continue;
+
+ oldPart = oldContentPart.GetPartById(relId);
+ newPart = newContentPart.AddNewPart<DiagramColorsPart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.cs).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images);
+ }
+
+ foreach (XElement oleReference in newContent.DescendantsAndSelf(O.OLEObject))
+ {
+ string relId = (string)oleReference.Attribute(R.id);
+
+ // First look to see if this relId has already been added to the new document.
+ // This is necessary for those parts that get processed with both old and new ids, such as the comments
+ // part. This is not necessary for parts such as the main document part, but this code won't malfunction
+ // in that case.
+ var ipp1 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (ipp1 != null)
+ {
+ OpenXmlPart tempPart = ipp1.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr1 = newContentPart.ExternalRelationships.FirstOrDefault(z => z.Id == relId);
+ if (tempEr1 != null)
+ continue;
+
+ var ipp4 = oldContentPart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp4 != null)
+ {
+ OpenXmlPart oldPart = oldContentPart.GetPartById(relId);
+ OpenXmlPart newPart = null;
+ if (oldPart is EmbeddedObjectPart)
+ {
+ if (newContentPart is HeaderPart)
+ newPart = ((HeaderPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ if (newContentPart is FooterPart)
+ newPart = ((FooterPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ if (newContentPart is MainDocumentPart)
+ newPart = ((MainDocumentPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ if (newContentPart is FootnotesPart)
+ newPart = ((FootnotesPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ if (newContentPart is EndnotesPart)
+ newPart = ((EndnotesPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ if (newContentPart is WordprocessingCommentsPart)
+ newPart = ((WordprocessingCommentsPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ }
+ else if (oldPart is EmbeddedPackagePart)
+ {
+ if (newContentPart is HeaderPart)
+ newPart = ((HeaderPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ if (newContentPart is FooterPart)
+ newPart = ((FooterPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ if (newContentPart is MainDocumentPart)
+ newPart = ((MainDocumentPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ if (newContentPart is FootnotesPart)
+ newPart = ((FootnotesPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ if (newContentPart is EndnotesPart)
+ newPart = ((EndnotesPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ if (newContentPart is WordprocessingCommentsPart)
+ newPart = ((WordprocessingCommentsPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ if (newContentPart is ChartPart)
+ newPart = ((ChartPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ }
+ using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0)
+ newObject.Write(buffer, 0, byteCount);
+ }
+ oleReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ }
+ else
+ {
+ if (relId != null)
+ {
+ ExternalRelationship er = oldContentPart.GetExternalRelationship(relId);
+ ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri);
+ oleReference.Attribute(R.id).Value = newEr.Id;
+ }
+ }
+ }
+
+ foreach (XElement chartReference in newContent.DescendantsAndSelf(C.chart))
+ {
+ string relId = (string)chartReference.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var ipp2 = newContentPart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp2 != null)
+ {
+ OpenXmlPart tempPart = ipp2.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr2 = newContentPart.ExternalRelationships.FirstOrDefault(z => z.Id == relId);
+ if (tempEr2 != null)
+ continue;
+
+ var ipp3 = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (ipp3 == null)
+ continue;
+ ChartPart oldPart = (ChartPart)ipp3.OpenXmlPart;
+ XDocument oldChart = oldPart.GetXDocument();
+ ChartPart newPart = newContentPart.AddNewPart<ChartPart>();
+ XDocument newChart = newPart.GetXDocument();
+ newChart.Add(oldChart.Root);
+ chartReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ CopyChartObjects(oldPart, newPart);
+ CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newChart.Root }, images);
+ }
+
+ foreach (XElement userShape in newContent.DescendantsAndSelf(C.userShapes))
+ {
+ string relId = (string)userShape.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+
+ var ipp4 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (ipp4 != null)
+ {
+ OpenXmlPart tempPart = ipp4.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr4 = newContentPart.ExternalRelationships.FirstOrDefault(z => z.Id == relId);
+ if (tempEr4 != null)
+ continue;
+
+ var ipp5 = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (ipp5 != null)
+ {
+ ChartDrawingPart oldPart = (ChartDrawingPart)ipp5.OpenXmlPart;
+ XDocument oldXDoc = oldPart.GetXDocument();
+ ChartDrawingPart newPart = newContentPart.AddNewPart<ChartDrawingPart>();
+ XDocument newXDoc = newPart.GetXDocument();
+ newXDoc.Add(oldXDoc.Root);
+ userShape.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, newContent);
+ CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newXDoc.Root }, images);
+ }
+ }
+ }
+
+ private static void CopyFontTable(FontTablePart oldFontTablePart, FontTablePart newFontTablePart)
+ {
+ var relevantElements = oldFontTablePart.GetXDocument().Descendants().Where(d => d.Name == W.embedRegular ||
+ d.Name == W.embedBold || d.Name == W.embedItalic || d.Name == W.embedBoldItalic).ToList();
+ foreach (XElement fontReference in relevantElements)
+ {
+ string relId = (string)fontReference.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+
+ var ipp1 = newFontTablePart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp1 != null)
+ {
+ OpenXmlPart tempPart = ipp1.OpenXmlPart;
+ continue;
+ }
+
+ ExternalRelationship tempEr1 = newFontTablePart.ExternalRelationships.FirstOrDefault(z => z.Id == relId);
+ if (tempEr1 != null)
+ continue;
+
+ FontPart oldPart = (FontPart)oldFontTablePart.GetPartById(relId);
+ FontPart newPart = newFontTablePart.AddFontPart(oldPart.ContentType);
+ var ResourceID = newFontTablePart.GetIdOfPart(newPart);
+ using (Stream oldFont = oldPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newFont = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldFont.Read(buffer, 0, 65536)) != 0)
+ newFont.Write(buffer, 0, byteCount);
+ }
+ fontReference.Attribute(R.id).Value = ResourceID;
+ }
+ }
+
+ private static void CopyChartObjects(ChartPart oldChart, ChartPart newChart)
+ {
+ foreach (XElement dataReference in newChart.GetXDocument().Descendants(C.externalData))
+ {
+ string relId = dataReference.Attribute(R.id).Value;
+
+ var ipp1 = oldChart.Parts.FirstOrDefault(z => z.RelationshipId == relId);
+ if (ipp1 != null)
+ {
+ var oldRelatedPart = ipp1.OpenXmlPart;
+ if (oldRelatedPart is EmbeddedPackagePart)
+ {
+ EmbeddedPackagePart oldPart = (EmbeddedPackagePart)ipp1.OpenXmlPart;
+ EmbeddedPackagePart newPart = newChart.AddEmbeddedPackagePart(oldPart.ContentType);
+ using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0)
+ newObject.Write(buffer, 0, byteCount);
+ }
+ dataReference.Attribute(R.id).Value = newChart.GetIdOfPart(newPart);
+ }
+ else if (oldRelatedPart is EmbeddedObjectPart)
+ {
+ EmbeddedObjectPart oldPart = (EmbeddedObjectPart)ipp1.OpenXmlPart;
+ var relType = oldRelatedPart.RelationshipType;
+ var conType = oldRelatedPart.ContentType;
+ string id = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 8);
+ var newPart = newChart.AddExtendedPart(relType, conType, ".bin", id);
+ using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0)
+ newObject.Write(buffer, 0, byteCount);
+ }
+ dataReference.Attribute(R.id).Value = newChart.GetIdOfPart(newPart);
+ }
+ }
+ else
+ {
+ ExternalRelationship oldRelationship = oldChart.GetExternalRelationship(relId);
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ throw new DocumentBuilderInternalException("Internal Error 0007");
+ newChart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ dataReference.Attribute(R.id).Value = newRid;
+ }
+ }
+ }
+
+ private static void CopyStartingParts(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ List<ImageData> images)
+ {
+ // A Core File Properties part does not have implicit or explicit relationships to other parts.
+ CoreFilePropertiesPart corePart = sourceDocument.CoreFilePropertiesPart;
+ if (corePart != null && corePart.GetXDocument().Root != null)
+ {
+ newDocument.AddCoreFilePropertiesPart();
+ XDocument newXDoc = newDocument.CoreFilePropertiesPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ XDocument sourceXDoc = corePart.GetXDocument();
+ newXDoc.Add(sourceXDoc.Root);
+ }
+
+ // An application attributes part does not have implicit or explicit relationships to other parts.
+ ExtendedFilePropertiesPart extPart = sourceDocument.ExtendedFilePropertiesPart;
+ if (extPart != null)
+ {
+ OpenXmlPart newPart = newDocument.AddExtendedFilePropertiesPart();
+ XDocument newXDoc = newDocument.ExtendedFilePropertiesPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(extPart.GetXDocument().Root);
+ }
+
+ // An custom file properties part does not have implicit or explicit relationships to other parts.
+ CustomFilePropertiesPart customPart = sourceDocument.CustomFilePropertiesPart;
+ if (customPart != null)
+ {
+ newDocument.AddCustomFilePropertiesPart();
+ XDocument newXDoc = newDocument.CustomFilePropertiesPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(customPart.GetXDocument().Root);
+ }
+
+ DocumentSettingsPart oldSettingsPart = sourceDocument.MainDocumentPart.DocumentSettingsPart;
+ if (oldSettingsPart != null)
+ {
+ DocumentSettingsPart newSettingsPart = newDocument.MainDocumentPart.AddNewPart<DocumentSettingsPart>();
+ XDocument settingsXDoc = oldSettingsPart.GetXDocument();
+ AddRelationships(oldSettingsPart, newSettingsPart, new[] { settingsXDoc.Root });
+ CopyFootnotesPart(sourceDocument, newDocument, settingsXDoc, images);
+ CopyEndnotesPart(sourceDocument, newDocument, settingsXDoc, images);
+ XDocument newXDoc = newDocument.MainDocumentPart.DocumentSettingsPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(settingsXDoc.Root);
+ CopyRelatedPartsForContentParts(oldSettingsPart, newSettingsPart, new[] { newXDoc.Root }, images);
+ }
+
+ WebSettingsPart oldWebSettingsPart = sourceDocument.MainDocumentPart.WebSettingsPart;
+ if (oldWebSettingsPart != null)
+ {
+ WebSettingsPart newWebSettingsPart = newDocument.MainDocumentPart.AddNewPart<WebSettingsPart>();
+ XDocument settingsXDoc = oldWebSettingsPart.GetXDocument();
+ AddRelationships(oldWebSettingsPart, newWebSettingsPart, new[] { settingsXDoc.Root });
+ XDocument newXDoc = newDocument.MainDocumentPart.WebSettingsPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(settingsXDoc.Root);
+ }
+
+ ThemePart themePart = sourceDocument.MainDocumentPart.ThemePart;
+ if (themePart != null)
+ {
+ ThemePart newThemePart = newDocument.MainDocumentPart.AddNewPart<ThemePart>();
+ XDocument newXDoc = newDocument.MainDocumentPart.ThemePart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(themePart.GetXDocument().Root);
+ CopyRelatedPartsForContentParts(themePart, newThemePart, new[] { newThemePart.GetXDocument().Root }, images);
+ }
+
+ // If needed to handle GlossaryDocumentPart in the future, then
+ // this code should handle the following parts:
+ // MainDocumentPart.GlossaryDocumentPart.StyleDefinitionsPart
+ // MainDocumentPart.GlossaryDocumentPart.StylesWithEffectsPart
+
+ // A Style Definitions part shall not have implicit or explicit relationships to any other part.
+ StyleDefinitionsPart stylesPart = sourceDocument.MainDocumentPart.StyleDefinitionsPart;
+ if (stylesPart != null)
+ {
+ newDocument.MainDocumentPart.AddNewPart<StyleDefinitionsPart>();
+ XDocument newXDoc = newDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(new XElement(W.styles,
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ stylesPart.GetXDocument().Descendants(W.docDefaults)));
+ MergeStyles(sourceDocument, newDocument, stylesPart.GetXDocument(), newXDoc, Enumerable.Empty<XElement>());
+ }
+
+ //// A StylesWithEffects part shall not have implicit or explicit relationships to any other part.
+ //StylesWithEffectsPart stylesWithEffectsPart = sourceDocument.MainDocumentPart.StylesWithEffectsPart;
+ //if (stylesWithEffectsPart != null)
+ //{
+ // newDocument.MainDocumentPart.AddNewPart<StylesWithEffectsPart>();
+ // XDocument newXDoc = newDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument();
+ // newXDoc.Declaration.Standalone = "yes";
+ // newXDoc.Declaration.Encoding = "UTF-8";
+ // newXDoc.Add(stylesWithEffectsPart.GetXDocument().Root);
+ //}
+
+
+
+ // Note: Do not copy the numbering part. For every source, create new numbering definitions from
+ // scratch.
+ //NumberingDefinitionsPart numberingPart = sourceDocument.MainDocumentPart.NumberingDefinitionsPart;
+ //if (numberingPart != null)
+ //{
+ // newDocument.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>();
+ // XDocument newXDoc = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument();
+ // newXDoc.Declaration.Standalone = "yes";
+ // newXDoc.Declaration.Encoding = "UTF-8";
+ // newXDoc.Add(numberingPart.GetXDocument().Root);
+ // newXDoc.Descendants(W.numIdMacAtCleanup).Remove();
+ //}
+
+ // A Font Table part shall not have any implicit or explicit relationships to any other part.
+ FontTablePart fontTablePart = sourceDocument.MainDocumentPart.FontTablePart;
+ if (fontTablePart != null)
+ {
+ newDocument.MainDocumentPart.AddNewPart<FontTablePart>();
+ XDocument newXDoc = newDocument.MainDocumentPart.FontTablePart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ CopyFontTable(sourceDocument.MainDocumentPart.FontTablePart, newDocument.MainDocumentPart.FontTablePart);
+ newXDoc.Add(fontTablePart.GetXDocument().Root);
+ }
+ }
+
+ private static void CopyFootnotesPart(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ XDocument settingsXDoc, List<ImageData> images)
+ {
+ int number = 0;
+ XDocument oldFootnotes = null;
+ XDocument newFootnotes = null;
+ XElement footnotePr = settingsXDoc.Root.Element(W.footnotePr);
+ if (footnotePr == null)
+ return;
+ foreach (XElement footnote in footnotePr.Elements(W.footnote))
+ {
+ if (oldFootnotes == null)
+ oldFootnotes = sourceDocument.MainDocumentPart.FootnotesPart.GetXDocument();
+ if (newFootnotes == null)
+ {
+ if (newDocument.MainDocumentPart.FootnotesPart != null)
+ {
+ newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument();
+ newFootnotes.Declaration.Standalone = "yes";
+ newFootnotes.Declaration.Encoding = "UTF-8";
+ var ids = newFootnotes.Root.Elements(W.footnote).Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ number = ids.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<FootnotesPart>();
+ newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument();
+ newFootnotes.Declaration.Standalone = "yes";
+ newFootnotes.Declaration.Encoding = "UTF-8";
+ newFootnotes.Add(new XElement(W.footnotes, NamespaceAttributes));
+ }
+ }
+ string id = (string)footnote.Attribute(W.id);
+ XElement element = oldFootnotes.Descendants()
+ .Elements(W.footnote)
+ .Where(p => ((string)p.Attribute(W.id)) == id)
+ .FirstOrDefault();
+ if (element != null)
+ {
+ XElement newElement = new XElement(element);
+ // the following adds the footnote into the new settings part
+ newElement.Attribute(W.id).Value = number.ToString();
+ newFootnotes.Root.Add(newElement);
+ footnote.Attribute(W.id).Value = number.ToString();
+ number++;
+ }
+ }
+ }
+
+ private static void CopyEndnotesPart(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ XDocument settingsXDoc, List<ImageData> images)
+ {
+ int number = 0;
+ XDocument oldEndnotes = null;
+ XDocument newEndnotes = null;
+ XElement endnotePr = settingsXDoc.Root.Element(W.endnotePr);
+ if (endnotePr == null)
+ return;
+ foreach (XElement endnote in endnotePr.Elements(W.endnote))
+ {
+ if (oldEndnotes == null)
+ oldEndnotes = sourceDocument.MainDocumentPart.EndnotesPart.GetXDocument();
+ if (newEndnotes == null)
+ {
+ if (newDocument.MainDocumentPart.EndnotesPart != null)
+ {
+ newEndnotes = newDocument.MainDocumentPart.EndnotesPart.GetXDocument();
+ newEndnotes.Declaration.Standalone = "yes";
+ newEndnotes.Declaration.Encoding = "UTF-8";
+ var ids = newEndnotes.Root
+ .Elements(W.endnote)
+ .Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ number = ids.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<EndnotesPart>();
+ newEndnotes = newDocument.MainDocumentPart.EndnotesPart.GetXDocument();
+ newEndnotes.Declaration.Standalone = "yes";
+ newEndnotes.Declaration.Encoding = "UTF-8";
+ newEndnotes.Add(new XElement(W.endnotes, NamespaceAttributes));
+ }
+ }
+ string id = (string)endnote.Attribute(W.id);
+ XElement element = oldEndnotes.Descendants()
+ .Elements(W.endnote)
+ .Where(p => ((string)p.Attribute(W.id)) == id)
+ .FirstOrDefault();
+ if (element != null)
+ {
+ XElement newElement = new XElement(element);
+ newElement.Attribute(W.id).Value = number.ToString();
+ newEndnotes.Root.Add(newElement);
+ endnote.Attribute(W.id).Value = number.ToString();
+ number++;
+ }
+ }
+ }
+
+ public static void FixRanges(XDocument sourceDocument, IEnumerable<XElement> newContent)
+ {
+ FixRange(sourceDocument,
+ newContent,
+ W.commentRangeStart,
+ W.commentRangeEnd,
+ W.id,
+ W.commentReference);
+ FixRange(sourceDocument,
+ newContent,
+ W.bookmarkStart,
+ W.bookmarkEnd,
+ W.id,
+ null);
+ FixRange(sourceDocument,
+ newContent,
+ W.permStart,
+ W.permEnd,
+ W.id,
+ null);
+ FixRange(sourceDocument,
+ newContent,
+ W.moveFromRangeStart,
+ W.moveFromRangeEnd,
+ W.id,
+ null);
+ FixRange(sourceDocument,
+ newContent,
+ W.moveToRangeStart,
+ W.moveToRangeEnd,
+ W.id,
+ null);
+ DeleteUnmatchedRange(sourceDocument,
+ newContent,
+ W.moveFromRangeStart,
+ W.moveFromRangeEnd,
+ W.moveToRangeStart,
+ W.name,
+ W.id);
+ DeleteUnmatchedRange(sourceDocument,
+ newContent,
+ W.moveToRangeStart,
+ W.moveToRangeEnd,
+ W.moveFromRangeStart,
+ W.name,
+ W.id);
+ }
+
+ private static void AddAtBeginning(IEnumerable<XElement> newContent, XElement contentToAdd)
+ {
+ if (newContent.First().Element(W.pPr) != null)
+ newContent.First().Element(W.pPr).AddAfterSelf(contentToAdd);
+ else
+ newContent.First().AddFirst(new XElement(contentToAdd));
+ }
+
+ private static void AddAtEnd(IEnumerable<XElement> newContent, XElement contentToAdd)
+ {
+ if (newContent.Last().Element(W.pPr) != null)
+ newContent.Last().Element(W.pPr).AddAfterSelf(new XElement(contentToAdd));
+ else
+ newContent.Last().Add(new XElement(contentToAdd));
+ }
+
+ // If the set of paragraphs from sourceDocument don't have a complete start/end for bookmarks,
+ // comments, etc., then this adds them to the paragraph. Note that this adds them to
+ // sourceDocument, and is impure.
+ private static void FixRange(XDocument sourceDocument, IEnumerable<XElement> newContent,
+ XName startElement, XName endElement, XName idAttribute, XName refElement)
+ {
+ foreach (XElement start in newContent.DescendantsAndSelf(startElement))
+ {
+ string rangeId = start.Attribute(idAttribute).Value;
+ if (newContent
+ .DescendantsAndSelf(endElement)
+ .Where(e => e.Attribute(idAttribute).Value == rangeId)
+ .Count() == 0)
+ {
+ XElement end = sourceDocument
+ .Descendants(endElement)
+ .Where(o => o.Attribute(idAttribute).Value == rangeId)
+ .FirstOrDefault();
+ if (end != null)
+ {
+ AddAtEnd(newContent, new XElement(end));
+ if (refElement != null)
+ {
+ XElement newRef = new XElement(refElement, new XAttribute(idAttribute, rangeId));
+ AddAtEnd(newContent, new XElement(newRef));
+ }
+ }
+ }
+ }
+ foreach (XElement end in newContent.Elements(endElement))
+ {
+ string rangeId = end.Attribute(idAttribute).Value;
+ if (newContent
+ .DescendantsAndSelf(startElement)
+ .Where(s => s.Attribute(idAttribute).Value == rangeId)
+ .Count() == 0)
+ {
+ XElement start = sourceDocument
+ .Descendants(startElement)
+ .Where(o => o.Attribute(idAttribute).Value == rangeId)
+ .FirstOrDefault();
+ if (start != null)
+ AddAtBeginning(newContent, new XElement(start));
+ }
+ }
+ }
+
+ private static void DeleteUnmatchedRange(XDocument sourceDocument, IEnumerable<XElement> newContent,
+ XName startElement, XName endElement, XName matchTo, XName matchAttr, XName idAttr)
+ {
+ List<string> deleteList = new List<string>();
+ foreach (XElement start in newContent.Elements(startElement))
+ {
+ string id = start.Attribute(matchAttr).Value;
+ if (!newContent.Elements(matchTo).Where(n => n.Attribute(matchAttr).Value == id).Any())
+ deleteList.Add(start.Attribute(idAttr).Value);
+ }
+ foreach (string item in deleteList)
+ {
+ newContent.Elements(startElement).Where(n => n.Attribute(idAttr).Value == item).Remove();
+ newContent.Elements(endElement).Where(n => n.Attribute(idAttr).Value == item).Remove();
+ newContent.Where(p => p.Name == startElement && p.Attribute(idAttr).Value == item).Remove();
+ newContent.Where(p => p.Name == endElement && p.Attribute(idAttr).Value == item).Remove();
+ }
+ }
+
+ private static void CopyFootnotes(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ IEnumerable<XElement> newContent, List<ImageData> images)
+ {
+ int number = 0;
+ XDocument oldFootnotes = null;
+ XDocument newFootnotes = null;
+ foreach (XElement footnote in newContent.DescendantsAndSelf(W.footnoteReference))
+ {
+ if (oldFootnotes == null)
+ oldFootnotes = sourceDocument.MainDocumentPart.FootnotesPart.GetXDocument();
+ if (newFootnotes == null)
+ {
+ if (newDocument.MainDocumentPart.FootnotesPart != null)
+ {
+ newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument();
+ var ids = newFootnotes
+ .Root
+ .Elements(W.footnote)
+ .Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ number = ids.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<FootnotesPart>();
+ newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument();
+ newFootnotes.Declaration.Standalone = "yes";
+ newFootnotes.Declaration.Encoding = "UTF-8";
+ newFootnotes.Add(new XElement(W.footnotes, NamespaceAttributes));
+ }
+ }
+ string id = (string)footnote.Attribute(W.id);
+ XElement element = oldFootnotes
+ .Descendants()
+ .Elements(W.footnote)
+ .Where(p => ((string)p.Attribute(W.id)) == id)
+ .FirstOrDefault();
+ if (element != null)
+ {
+ XElement newElement = new XElement(element);
+ newElement.Attribute(W.id).Value = number.ToString();
+ newFootnotes.Root.Add(newElement);
+ footnote.Attribute(W.id).Value = number.ToString();
+ number++;
+ }
+ }
+ if (sourceDocument.MainDocumentPart.FootnotesPart != null &&
+ newDocument.MainDocumentPart.FootnotesPart != null)
+ {
+ AddRelationships(sourceDocument.MainDocumentPart.FootnotesPart,
+ newDocument.MainDocumentPart.FootnotesPart,
+ new[] { newDocument.MainDocumentPart.FootnotesPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.FootnotesPart,
+ newDocument.MainDocumentPart.FootnotesPart,
+ new[] { newDocument.MainDocumentPart.FootnotesPart.GetXDocument().Root }, images);
+ }
+ }
+
+ private static void CopyEndnotes(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument,
+ IEnumerable<XElement> newContent, List<ImageData> images)
+ {
+ int number = 0;
+ XDocument oldEndnotes = null;
+ XDocument newEndnotes = null;
+ foreach (XElement endnote in newContent.DescendantsAndSelf(W.endnoteReference))
+ {
+ if (oldEndnotes == null)
+ oldEndnotes = sourceDocument.MainDocumentPart.EndnotesPart.GetXDocument();
+ if (newEndnotes == null)
+ {
+ if (newDocument.MainDocumentPart.EndnotesPart != null)
+ {
+ newEndnotes = newDocument
+ .MainDocumentPart
+ .EndnotesPart
+ .GetXDocument();
+ var ids = newEndnotes
+ .Root
+ .Elements(W.endnote)
+ .Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ number = ids.Max() + 1;
+ }
+ else
+ {
+ newDocument.MainDocumentPart.AddNewPart<EndnotesPart>();
+ newEndnotes = newDocument.MainDocumentPart.EndnotesPart.GetXDocument();
+ newEndnotes.Declaration.Standalone = "yes";
+ newEndnotes.Declaration.Encoding = "UTF-8";
+ newEndnotes.Add(new XElement(W.endnotes, NamespaceAttributes));
+ }
+ }
+ string id = (string)endnote.Attribute(W.id);
+ XElement element = oldEndnotes
+ .Descendants()
+ .Elements(W.endnote)
+ .Where(p => ((string)p.Attribute(W.id)) == id)
+ .First();
+ XElement newElement = new XElement(element);
+ newElement.Attribute(W.id).Value = number.ToString();
+ newEndnotes.Root.Add(newElement);
+ endnote.Attribute(W.id).Value = number.ToString();
+ number++;
+ }
+ if (sourceDocument.MainDocumentPart.EndnotesPart != null &&
+ newDocument.MainDocumentPart.EndnotesPart != null)
+ {
+ AddRelationships(sourceDocument.MainDocumentPart.EndnotesPart,
+ newDocument.MainDocumentPart.EndnotesPart,
+ new[] { newDocument.MainDocumentPart.EndnotesPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.EndnotesPart,
+ newDocument.MainDocumentPart.EndnotesPart,
+ new[] { newDocument.MainDocumentPart.EndnotesPart.GetXDocument().Root }, images);
+ }
+ }
+
+ // General function for handling images that tries to use an existing image if they are the same
+ private static ImageData ManageImageCopy(ImagePart oldImage, OpenXmlPart newContentPart, List<ImageData> images)
+ {
+ ImageData oldImageData = new ImageData(oldImage);
+ foreach (ImageData item in images)
+ {
+ if (newContentPart != item.ImagePart)
+ continue;
+ if (item.Compare(oldImageData))
+ return item;
+ }
+ images.Add(oldImageData);
+ return oldImageData;
+ }
+
+ private static XAttribute[] NamespaceAttributes =
+ {
+ new XAttribute(XNamespace.Xmlns + "wpc", WPC.wpc),
+ new XAttribute(XNamespace.Xmlns + "mc", MC.mc),
+ new XAttribute(XNamespace.Xmlns + "o", O.o),
+ new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XAttribute(XNamespace.Xmlns + "m", M.m),
+ new XAttribute(XNamespace.Xmlns + "v", VML.vml),
+ new XAttribute(XNamespace.Xmlns + "wp14", WP14.wp14),
+ new XAttribute(XNamespace.Xmlns + "wp", WP.wp),
+ new XAttribute(XNamespace.Xmlns + "w10", W10.w10),
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(XNamespace.Xmlns + "w14", W14.w14),
+ new XAttribute(XNamespace.Xmlns + "wpg", WPG.wpg),
+ new XAttribute(XNamespace.Xmlns + "wpi", WPI.wpi),
+ new XAttribute(XNamespace.Xmlns + "wne", WNE.wne),
+ new XAttribute(XNamespace.Xmlns + "wps", WPS.wps),
+ new XAttribute(MC.Ignorable, "w14 wp14"),
+ };
+ }
+
+ public class DocumentBuilderException : Exception
+ {
+ public DocumentBuilderException(string message) : base(message) { }
+ }
+
+ public class DocumentBuilderInternalException : Exception
+ {
+ public DocumentBuilderInternalException(string message) : base(message) { }
+ }
+}
diff --git a/OpenXmlPowerTools/ExcelFormula.cs b/OpenXmlPowerTools/ExcelFormula.cs
new file mode 100644
index 0000000..d85705e
--- /dev/null
+++ b/OpenXmlPowerTools/ExcelFormula.cs
@@ -0,0 +1,830 @@
+/* created on 9/8/2012 9:28:14 AM from peg generator V1.0 using 'ExcelFormula.txt' as input*/
+
+using Peg.Base;
+using System;
+using System.IO;
+using System.Text;
+namespace ExcelFormula
+{
+
+ enum EExcelFormula{Formula= 1, Expression= 2, InfixTerms= 3, PreAndPostTerm= 4,
+ Term= 5, RefInfixTerms= 6, RefTerm= 7, Constant= 8, RefConstant= 9,
+ ErrorConstant= 10, LogicalConstant= 11, NumericalConstant= 12,
+ SignificandPart= 13, WholeNumberPart= 14, FractionalPart= 15,
+ ExponentPart= 16, StringConstant= 17, StringCharacter= 18, HighCharacter= 19,
+ ArrayConstant= 20, ConstantListRows= 21, ConstantListRow= 22,
+ InfixOperator= 23, ValueInfixOperator= 24, RefInfixOperator= 25,
+ UnionOperator= 26, IntersectionOperator= 27, RangeOperator= 28,
+ PostfixOperator= 29, PrefixOperator= 30, CellReference= 31, LocalCellReference= 32,
+ ExternalCellReference= 33, BookPrefix= 34, BangReference= 35,
+ SheetRangeReference= 36, SingleSheetPrefix= 37, SingleSheetReference= 38,
+ SingleSheetArea= 39, SingleSheet= 40, SheetRange= 41, WorkbookIndex= 42,
+ SheetName= 43, SheetNameCharacter= 44, SheetNameSpecial= 45,
+ SheetNameBaseCharacter= 46, A1Reference= 47, A1Cell= 48, A1Area= 49,
+ A1Column= 50, A1AbsoluteColumn= 51, A1RelativeColumn= 52, A1Row= 53,
+ A1AbsoluteRow= 54, A1RelativeRow= 55, CellFunctionCall= 56, UserDefinedFunctionCall= 57,
+ UserDefinedFunctionName= 58, ArgumentList= 59, Argument= 60,
+ ArgumentExpression= 61, ArgumentInfixTerms= 62, ArgumentPreAndPostTerm= 63,
+ ArgumentTerm= 64, ArgumentRefInfixTerms= 65, ArgumentRefTerm= 66,
+ ArgumentInfixOperator= 67, RefArgumentInfixOperator= 68, NameReference= 69,
+ ExternalName= 70, BangName= 71, Name= 72, NameStartCharacter= 73,
+ NameCharacter= 74, StructureReference= 75, TableIdentifier= 76,
+ TableName= 77, IntraTableReference= 78, InnerReference= 79, Keyword= 80,
+ KeywordList= 81, ColumnRange= 82, Column= 83, SimpleColumnName= 84,
+ EscapeColumnCharacter= 85, UnescapedColumnCharacter= 86, AnyNoSpaceColumnCharacter= 87,
+ SpacedComma= 88, SpacedLBracket= 89, SpacedRBracket= 90, ws= 91};
+ class ExcelFormula : PegCharParser
+ {
+
+ #region Input Properties
+ public static EncodingClass encodingClass = EncodingClass.ascii;
+ public static UnicodeDetection unicodeDetection = UnicodeDetection.notApplicable;
+ #endregion Input Properties
+ #region Constructors
+ public ExcelFormula()
+ : base()
+ {
+
+ }
+ public ExcelFormula(string src,TextWriter FerrOut)
+ : base(src,FerrOut)
+ {
+
+ }
+ #endregion Constructors
+ #region Overrides
+ public override string GetRuleNameFromId(int id)
+ {
+ try
+ {
+ EExcelFormula ruleEnum = (EExcelFormula)id;
+ string s= ruleEnum.ToString();
+ int val;
+ if( int.TryParse(s,out val) ){
+ return base.GetRuleNameFromId(id);
+ }else{
+ return s;
+ }
+ }
+ catch (Exception)
+ {
+ return base.GetRuleNameFromId(id);
+ }
+ }
+ public override void GetProperties(out EncodingClass encoding, out UnicodeDetection detection)
+ {
+ encoding = encodingClass;
+ detection = unicodeDetection;
+ }
+ #endregion Overrides
+ #region Grammar Rules
+ public bool Formula() /*Formula: Expression (!./FATAL<"end of line expected">);*/
+ {
+
+ return And(()=>
+ Expression()
+ && ( Not(()=> Any() ) || Fatal("end of line expected")) );
+ }
+ public bool Expression() /*Expression: ws InfixTerms;*/
+ {
+
+ return And(()=> ws() && InfixTerms() );
+ }
+ public bool InfixTerms() /*InfixTerms: PreAndPostTerm (InfixOperator ws PreAndPostTerm)*;*/
+ {
+
+ return And(()=>
+ PreAndPostTerm()
+ && OptRepeat(()=>
+ And(()=>
+ InfixOperator()
+ && ws()
+ && PreAndPostTerm() ) ) );
+ }
+ public bool PreAndPostTerm() /*PreAndPostTerm: (PrefixOperator ws)* Term (PostfixOperator ws)*;*/
+ {
+
+ return And(()=>
+ OptRepeat(()=> And(()=> PrefixOperator() && ws() ) )
+ && Term()
+ && OptRepeat(()=> And(()=> PostfixOperator() && ws() ) ) );
+ }
+ public bool Term() /*Term: (RefInfixTerms / '(' Expression ')' / Constant) ws;*/
+ {
+
+ return And(()=>
+ (
+ RefInfixTerms()
+ || And(()=> Char('(') && Expression() && Char(')') )
+ || Constant())
+ && ws() );
+ }
+ public bool RefInfixTerms() /*RefInfixTerms: RefTerm (RefInfixOperator ws RefTerm)*;*/
+ {
+
+ return And(()=>
+ RefTerm()
+ && OptRepeat(()=>
+ And(()=> RefInfixOperator() && ws() && RefTerm() ) ) );
+ }
+ public bool RefTerm() /*RefTerm: '(' ws RefInfixTerms ')' / RefConstant / CellFunctionCall / CellReference / UserDefinedFunctionCall
+ / NameReference / StructureReference;*/
+ {
+
+ return
+ And(()=>
+ Char('(')
+ && ws()
+ && RefInfixTerms()
+ && Char(')') )
+ || RefConstant()
+ || CellFunctionCall()
+ || CellReference()
+ || UserDefinedFunctionCall()
+ || NameReference()
+ || StructureReference();
+ }
+ public bool Constant() /*^^Constant: ErrorConstant / LogicalConstant / NumericalConstant / StringConstant / ArrayConstant;*/
+ {
+
+ return TreeNT((int)EExcelFormula.Constant,()=>
+
+ ErrorConstant()
+ || LogicalConstant()
+ || NumericalConstant()
+ || StringConstant()
+ || ArrayConstant() );
+ }
+ public bool RefConstant() /*RefConstant: '#REF!';*/
+ {
+
+ return Char('#','R','E','F','!');
+ }
+ public bool ErrorConstant() /*ErrorConstant: RefConstant / '#DIV/0!' / '#N/A' / '#NAME?' / '#NULL!' / '#NUM!' / '#VALUE!' / '#GETTING_DATA';*/
+ {
+
+ return
+ RefConstant()
+ || Char('#','D','I','V','/','0','!')
+ || Char('#','N','/','A')
+ || Char('#','N','A','M','E','?')
+ || Char('#','N','U','L','L','!')
+ || Char('#','N','U','M','!')
+ || Char('#','V','A','L','U','E','!')
+ || Char("#GETTING_DATA");
+ }
+ public bool LogicalConstant() /*LogicalConstant: 'FALSE' / 'TRUE';*/
+ {
+
+ return Char('F','A','L','S','E') || Char('T','R','U','E');
+ }
+ public bool NumericalConstant() /*NumericalConstant: '-'? SignificandPart ExponentPart?;*/
+ {
+
+ return And(()=>
+ Option(()=> Char('-') )
+ && SignificandPart()
+ && Option(()=> ExponentPart() ) );
+ }
+ public bool SignificandPart() /*SignificandPart: WholeNumberPart FractionalPart? / FractionalPart;*/
+ {
+
+ return
+ And(()=>
+ WholeNumberPart()
+ && Option(()=> FractionalPart() ) )
+ || FractionalPart();
+ }
+ public bool WholeNumberPart() /*WholeNumberPart: [0-9]+;*/
+ {
+
+ return PlusRepeat(()=> In('0','9') );
+ }
+ public bool FractionalPart() /*FractionalPart: '.' [0-9]*;*/
+ {
+
+ return And(()=> Char('.') && OptRepeat(()=> In('0','9') ) );
+ }
+ public bool ExponentPart() /*ExponentPart: 'E' ('+' / '-')? [0-9]*;*/
+ {
+
+ return And(()=>
+ Char('E')
+ && Option(()=> Char('+') || Char('-') )
+ && OptRepeat(()=> In('0','9') ) );
+ }
+ public bool StringConstant() /*StringConstant: '"' ('""'/StringCharacter)* '"';*/
+ {
+
+ return And(()=>
+ Char('"')
+ && OptRepeat(()=> Char('"','"') || StringCharacter() )
+ && Char('"') );
+ }
+ public bool StringCharacter() /*StringCharacter: [#-~] / '!' / ' ' / HighCharacter;*/
+ {
+
+ return
+ In('#','~')
+ || Char('!')
+ || Char(' ')
+ || HighCharacter();
+ }
+ public bool HighCharacter() /*HighCharacter: [#x80-#xFFFF];*/
+ {
+
+ return In('\u0080','\uffff');
+ }
+ public bool ArrayConstant() /*^^ArrayConstant: '{' ConstantListRows '}';*/
+ {
+
+ return TreeNT((int)EExcelFormula.ArrayConstant,()=>
+ And(()=> Char('{') && ConstantListRows() && Char('}') ) );
+ }
+ public bool ConstantListRows() /*ConstantListRows: ConstantListRow (';' ConstantListRow)*;*/
+ {
+
+ return And(()=>
+ ConstantListRow()
+ && OptRepeat(()=>
+ And(()=> Char(';') && ConstantListRow() ) ) );
+ }
+ public bool ConstantListRow() /*^^ConstantListRow: Constant (',' Constant)*;*/
+ {
+
+ return TreeNT((int)EExcelFormula.ConstantListRow,()=>
+ And(()=>
+ Constant()
+ && OptRepeat(()=> And(()=> Char(',') && Constant() ) ) ) );
+ }
+ public bool InfixOperator() /*InfixOperator: RefInfixOperator / ValueInfixOperator;*/
+ {
+
+ return RefInfixOperator() || ValueInfixOperator();
+ }
+ public bool ValueInfixOperator() /*^^ValueInfixOperator: '<>' / '>=' / '<=' / '^' / '*' / '/' / '+' / '-' / '&' / '=' / '<' / '>';*/
+ {
+
+ return TreeNT((int)EExcelFormula.ValueInfixOperator,()=>
+ OneOfLiterals(optimizedLiterals0) );
+ }
+ public bool RefInfixOperator() /*RefInfixOperator: RangeOperator / UnionOperator / IntersectionOperator;*/
+ {
+
+ return
+ RangeOperator()
+ || UnionOperator()
+ || IntersectionOperator();
+ }
+ public bool UnionOperator() /*^^UnionOperator: ',';*/
+ {
+
+ return TreeNT((int)EExcelFormula.UnionOperator,()=>
+ Char(',') );
+ }
+ public bool IntersectionOperator() /*^^IntersectionOperator: ' ';*/
+ {
+
+ return TreeNT((int)EExcelFormula.IntersectionOperator,()=>
+ Char(' ') );
+ }
+ public bool RangeOperator() /*^^RangeOperator: ':';*/
+ {
+
+ return TreeNT((int)EExcelFormula.RangeOperator,()=>
+ Char(':') );
+ }
+ public bool PostfixOperator() /*^^PostfixOperator: '%';*/
+ {
+
+ return TreeNT((int)EExcelFormula.PostfixOperator,()=>
+ Char('%') );
+ }
+ public bool PrefixOperator() /*^^PrefixOperator: '+' / '-';*/
+ {
+
+ return TreeNT((int)EExcelFormula.PrefixOperator,()=>
+ Char('+') || Char('-') );
+ }
+ public bool CellReference() /*CellReference: ExternalCellReference / LocalCellReference;*/
+ {
+
+ return ExternalCellReference() || LocalCellReference();
+ }
+ public bool LocalCellReference() /*LocalCellReference: A1Reference;*/
+ {
+
+ return A1Reference();
+ }
+ public bool ExternalCellReference() /*ExternalCellReference: BangReference / SheetRangeReference / SingleSheetReference;*/
+ {
+
+ return
+ BangReference()
+ || SheetRangeReference()
+ || SingleSheetReference();
+ }
+ public bool BookPrefix() /*BookPrefix: WorkbookIndex '!';*/
+ {
+
+ return And(()=> WorkbookIndex() && Char('!') );
+ }
+ public bool BangReference() /*BangReference: '!' (A1Reference / '#REF!');*/
+ {
+
+ return And(()=>
+ Char('!')
+ && ( A1Reference() || Char('#','R','E','F','!')) );
+ }
+ public bool SheetRangeReference() /*SheetRangeReference: SheetRange '!' A1Reference;*/
+ {
+
+ return And(()=> SheetRange() && Char('!') && A1Reference() );
+ }
+ public bool SingleSheetPrefix() /*SingleSheetPrefix: SingleSheet '!';*/
+ {
+
+ return And(()=> SingleSheet() && Char('!') );
+ }
+ public bool SingleSheetReference() /*SingleSheetReference: SingleSheetPrefix (A1Reference / '#REF!');*/
+ {
+
+ return And(()=>
+ SingleSheetPrefix()
+ && ( A1Reference() || Char('#','R','E','F','!')) );
+ }
+ public bool SingleSheetArea() /*SingleSheetArea: SingleSheetPrefix A1Area;*/
+ {
+
+ return And(()=> SingleSheetPrefix() && A1Area() );
+ }
+ public bool SingleSheet() /*SingleSheet: WorkbookIndex? SheetName / '\'' WorkbookIndex? SheetNameSpecial '\'';*/
+ {
+
+ return
+ And(()=>
+ Option(()=> WorkbookIndex() )
+ && SheetName() )
+ || And(()=>
+ Char('\'')
+ && Option(()=> WorkbookIndex() )
+ && SheetNameSpecial()
+ && Char('\'') );
+ }
+ public bool SheetRange() /*SheetRange: WorkbookIndex? SheetName ':' SheetName / '\'' WorkbookIndex? SheetNameSpecial ':' SheetNameSpecial '\'';*/
+ {
+
+ return
+ And(()=>
+ Option(()=> WorkbookIndex() )
+ && SheetName()
+ && Char(':')
+ && SheetName() )
+ || And(()=>
+ Char('\'')
+ && Option(()=> WorkbookIndex() )
+ && SheetNameSpecial()
+ && Char(':')
+ && SheetNameSpecial()
+ && Char('\'') );
+ }
+ public bool WorkbookIndex() /*^^WorkbookIndex: '[' WholeNumberPart ']';*/
+ {
+
+ return TreeNT((int)EExcelFormula.WorkbookIndex,()=>
+ And(()=> Char('[') && WholeNumberPart() && Char(']') ) );
+ }
+ public bool SheetName() /*^^SheetName: SheetNameCharacter+;*/
+ {
+
+ return TreeNT((int)EExcelFormula.SheetName,()=>
+ PlusRepeat(()=> SheetNameCharacter() ) );
+ }
+ public bool SheetNameCharacter() /*SheetNameCharacter: [A-Za-z0-9._] / HighCharacter;*/
+ {
+
+ return
+ (In('A','Z', 'a','z', '0','9')||OneOf("._"))
+ || HighCharacter();
+ }
+ public bool SheetNameSpecial() /*^^SheetNameSpecial: SheetNameBaseCharacter ('\'\''* SheetNameBaseCharacter)*;*/
+ {
+
+ return TreeNT((int)EExcelFormula.SheetNameSpecial,()=>
+ And(()=>
+ SheetNameBaseCharacter()
+ && OptRepeat(()=>
+ And(()=>
+ OptRepeat(()=> Char('\'','\'') )
+ && SheetNameBaseCharacter() ) ) ) );
+ }
+ public bool SheetNameBaseCharacter() /*SheetNameBaseCharacter: [A-Za-z0-9!"#$%&()+,-.;<=>@^_`{|}~ ] / HighCharacter;*/
+ {
+
+ return OneOf(optimizedCharset0) || HighCharacter();
+ }
+ public bool A1Reference() /*^^A1Reference: (A1Column ':' A1Column) / (A1Row ':' A1Row) / A1Area / A1Cell;*/
+ {
+
+ return TreeNT((int)EExcelFormula.A1Reference,()=>
+
+ And(()=> A1Column() && Char(':') && A1Column() )
+ || And(()=> A1Row() && Char(':') && A1Row() )
+ || A1Area()
+ || A1Cell() );
+ }
+ public bool A1Cell() /*A1Cell: A1Column A1Row !NameCharacter;*/
+ {
+
+ return And(()=>
+ A1Column()
+ && A1Row()
+ && Not(()=> NameCharacter() ) );
+ }
+ public bool A1Area() /*A1Area: A1Cell ':' A1Cell;*/
+ {
+
+ return And(()=> A1Cell() && Char(':') && A1Cell() );
+ }
+ public bool A1Column() /*^^A1Column: A1AbsoluteColumn / A1RelativeColumn;*/
+ {
+
+ return TreeNT((int)EExcelFormula.A1Column,()=>
+ A1AbsoluteColumn() || A1RelativeColumn() );
+ }
+ public bool A1AbsoluteColumn() /*A1AbsoluteColumn: '$' A1RelativeColumn;*/
+ {
+
+ return And(()=> Char('$') && A1RelativeColumn() );
+ }
+ public bool A1RelativeColumn() /*A1RelativeColumn: 'XF' [A-D] / 'X' [A-E] [A-Z] / [A-W][A-Z][A-Z] / [A-Z][A-Z] / [A-Z];*/
+ {
+
+ return
+ And(()=> Char('X','F') && In('A','D') )
+ || And(()=> Char('X') && In('A','E') && In('A','Z') )
+ || And(()=> In('A','W') && In('A','Z') && In('A','Z') )
+ || And(()=> In('A','Z') && In('A','Z') )
+ || In('A','Z');
+ }
+ public bool A1Row() /*^^A1Row: A1AbsoluteRow / A1RelativeRow;*/
+ {
+
+ return TreeNT((int)EExcelFormula.A1Row,()=>
+ A1AbsoluteRow() || A1RelativeRow() );
+ }
+ public bool A1AbsoluteRow() /*A1AbsoluteRow: '$' A1RelativeRow;*/
+ {
+
+ return And(()=> Char('$') && A1RelativeRow() );
+ }
+ public bool A1RelativeRow() /*A1RelativeRow: [1-9][0-9]*;*/
+ {
+
+ return And(()=> In('1','9') && OptRepeat(()=> In('0','9') ) );
+ }
+ public bool CellFunctionCall() /*^^CellFunctionCall: A1Cell '(' ArgumentList ')';*/
+ {
+
+ return TreeNT((int)EExcelFormula.CellFunctionCall,()=>
+ And(()=>
+ A1Cell()
+ && Char('(')
+ && ArgumentList()
+ && Char(')') ) );
+ }
+ public bool UserDefinedFunctionCall() /*^^UserDefinedFunctionCall: UserDefinedFunctionName '(' ArgumentList ')';*/
+ {
+
+ return TreeNT((int)EExcelFormula.UserDefinedFunctionCall,()=>
+ And(()=>
+ UserDefinedFunctionName()
+ && Char('(')
+ && ArgumentList()
+ && Char(')') ) );
+ }
+ public bool UserDefinedFunctionName() /*UserDefinedFunctionName: NameReference;*/
+ {
+
+ return NameReference();
+ }
+ public bool ArgumentList() /*ArgumentList: Argument (',' Argument)*;*/
+ {
+
+ return And(()=>
+ Argument()
+ && OptRepeat(()=> And(()=> Char(',') && Argument() ) ) );
+ }
+ public bool Argument() /*Argument: ArgumentExpression / ws;*/
+ {
+
+ return ArgumentExpression() || ws();
+ }
+ public bool ArgumentExpression() /*^^ArgumentExpression: ws ArgumentInfixTerms;*/
+ {
+
+ return TreeNT((int)EExcelFormula.ArgumentExpression,()=>
+ And(()=> ws() && ArgumentInfixTerms() ) );
+ }
+ public bool ArgumentInfixTerms() /*ArgumentInfixTerms: ArgumentPreAndPostTerm (ArgumentInfixOperator ws ArgumentPreAndPostTerm)*;*/
+ {
+
+ return And(()=>
+ ArgumentPreAndPostTerm()
+ && OptRepeat(()=>
+ And(()=>
+ ArgumentInfixOperator()
+ && ws()
+ && ArgumentPreAndPostTerm() ) ) );
+ }
+ public bool ArgumentPreAndPostTerm() /*ArgumentPreAndPostTerm: (PrefixOperator ws)* ArgumentTerm (PostfixOperator ws)*;*/
+ {
+
+ return And(()=>
+ OptRepeat(()=> And(()=> PrefixOperator() && ws() ) )
+ && ArgumentTerm()
+ && OptRepeat(()=> And(()=> PostfixOperator() && ws() ) ) );
+ }
+ public bool ArgumentTerm() /*ArgumentTerm: (ArgumentRefInfixTerms / '(' Expression ')' / Constant) ws;*/
+ {
+
+ return And(()=>
+ (
+ ArgumentRefInfixTerms()
+ || And(()=> Char('(') && Expression() && Char(')') )
+ || Constant())
+ && ws() );
+ }
+ public bool ArgumentRefInfixTerms() /*ArgumentRefInfixTerms: ArgumentRefTerm (RefArgumentInfixOperator ws ArgumentRefTerm)*;*/
+ {
+
+ return And(()=>
+ ArgumentRefTerm()
+ && OptRepeat(()=>
+ And(()=>
+ RefArgumentInfixOperator()
+ && ws()
+ && ArgumentRefTerm() ) ) );
+ }
+ public bool ArgumentRefTerm() /*ArgumentRefTerm: '(' ws RefInfixTerms ')' / RefConstant / CellFunctionCall / CellReference / UserDefinedFunctionCall
+ / NameReference / StructureReference;*/
+ {
+
+ return
+ And(()=>
+ Char('(')
+ && ws()
+ && RefInfixTerms()
+ && Char(')') )
+ || RefConstant()
+ || CellFunctionCall()
+ || CellReference()
+ || UserDefinedFunctionCall()
+ || NameReference()
+ || StructureReference();
+ }
+ public bool ArgumentInfixOperator() /*ArgumentInfixOperator: RefArgumentInfixOperator / ValueInfixOperator;*/
+ {
+
+ return RefArgumentInfixOperator() || ValueInfixOperator();
+ }
+ public bool RefArgumentInfixOperator() /*RefArgumentInfixOperator: RangeOperator / IntersectionOperator;*/
+ {
+
+ return RangeOperator() || IntersectionOperator();
+ }
+ public bool NameReference() /*^^NameReference: (ExternalName / Name) !'[';*/
+ {
+
+ return TreeNT((int)EExcelFormula.NameReference,()=>
+ And(()=>
+ ( ExternalName() || Name())
+ && Not(()=> Char('[') ) ) );
+ }
+ public bool ExternalName() /*ExternalName: BangName / (SingleSheetPrefix / BookPrefix) Name;*/
+ {
+
+ return
+ BangName()
+ || And(()=>
+ ( SingleSheetPrefix() || BookPrefix())
+ && Name() );
+ }
+ public bool BangName() /*BangName: '!' Name;*/
+ {
+
+ return And(()=> Char('!') && Name() );
+ }
+ public bool Name() /*Name: NameStartCharacter NameCharacter*;*/
+ {
+
+ return And(()=>
+ NameStartCharacter()
+ && OptRepeat(()=> NameCharacter() ) );
+ }
+ public bool NameStartCharacter() /*NameStartCharacter: [_\\A-Za-z] / HighCharacter;*/
+ {
+
+ return
+ (In('A','Z', 'a','z')||OneOf("_\\"))
+ || HighCharacter();
+ }
+ public bool NameCharacter() /*NameCharacter: NameStartCharacter / [0-9] / '.' / '?' / HighCharacter;*/
+ {
+
+ return
+ NameStartCharacter()
+ || In('0','9')
+ || Char('.')
+ || Char('?')
+ || HighCharacter();
+ }
+ public bool StructureReference() /*^^StructureReference: TableIdentifier? IntraTableReference;*/
+ {
+
+ return TreeNT((int)EExcelFormula.StructureReference,()=>
+ And(()=>
+ Option(()=> TableIdentifier() )
+ && IntraTableReference() ) );
+ }
+ public bool TableIdentifier() /*TableIdentifier: BookPrefix? TableName;*/
+ {
+
+ return And(()=> Option(()=> BookPrefix() ) && TableName() );
+ }
+ public bool TableName() /*TableName: Name;*/
+ {
+
+ return Name();
+ }
+ public bool IntraTableReference() /*IntraTableReference: SpacedLBracket InnerReference SpacedRBracket / Keyword / '[' SimpleColumnName ']';*/
+ {
+
+ return
+ And(()=>
+ SpacedLBracket()
+ && InnerReference()
+ && SpacedRBracket() )
+ || Keyword()
+ || And(()=>
+ Char('[')
+ && SimpleColumnName()
+ && Char(']') );
+ }
+ public bool InnerReference() /*InnerReference: (KeywordList SpacedComma)? ColumnRange / KeywordList;*/
+ {
+
+ return
+ And(()=>
+ Option(()=>
+ And(()=> KeywordList() && SpacedComma() ) )
+ && ColumnRange() )
+ || KeywordList();
+ }
+ public bool Keyword() /*Keyword: '[#All]' / '[#Data]' / '[#Headers]' / '[#Totals]' / '[#This Row]';*/
+ {
+
+ return
+ Char('[','#','A','l','l',']')
+ || Char('[','#','D','a','t','a',']')
+ || Char("[#Headers]")
+ || Char("[#Totals]")
+ || Char("[#This Row]");
+ }
+ public bool KeywordList() /*KeywordList: '[#Headers]' SpacedComma '[#Data]' / '[#Data]' SpacedComma '[#Totals]' / Keyword;*/
+ {
+
+ return
+ And(()=>
+ Char("[#Headers]")
+ && SpacedComma()
+ && Char('[','#','D','a','t','a',']') )
+ || And(()=>
+ Char('[','#','D','a','t','a',']')
+ && SpacedComma()
+ && Char("[#Totals]") )
+ || Keyword();
+ }
+ public bool ColumnRange() /*ColumnRange: Column (':' Column)?;*/
+ {
+
+ return And(()=>
+ Column()
+ && Option(()=> And(()=> Char(':') && Column() ) ) );
+ }
+ public bool Column() /*Column: '[' ws SimpleColumnName ws ']' / SimpleColumnName;*/
+ {
+
+ return
+ And(()=>
+ Char('[')
+ && ws()
+ && SimpleColumnName()
+ && ws()
+ && Char(']') )
+ || SimpleColumnName();
+ }
+ public bool SimpleColumnName() /*SimpleColumnName: AnyNoSpaceColumnCharacter+ (ws AnyNoSpaceColumnCharacter+)*;*/
+ {
+
+ return And(()=>
+ PlusRepeat(()=> AnyNoSpaceColumnCharacter() )
+ && OptRepeat(()=>
+ And(()=>
+ ws()
+ && PlusRepeat(()=> AnyNoSpaceColumnCharacter() ) ) ) );
+ }
+ public bool EscapeColumnCharacter() /*EscapeColumnCharacter: '\'' / '#' / '[' / ']';*/
+ {
+
+ return Char('\'') || Char('#') || Char('[') || Char(']');
+ }
+ public bool UnescapedColumnCharacter() /*UnescapedColumnCharacter: [A-Za-z0-9!"#$%&()*+,-./:;<=>?@\\^_`{|}~] / HighCharacter;*/
+ {
+
+ return OneOf(optimizedCharset1) || HighCharacter();
+ }
+ public bool AnyNoSpaceColumnCharacter() /*AnyNoSpaceColumnCharacter: ('\'' EscapeColumnCharacter) / UnescapedColumnCharacter;*/
+ {
+
+ return
+ And(()=> Char('\'') && EscapeColumnCharacter() )
+ || UnescapedColumnCharacter();
+ }
+ public bool SpacedComma() /*SpacedComma: ' '? ',' ' '?;*/
+ {
+
+ return And(()=>
+ Option(()=> Char(' ') )
+ && Char(',')
+ && Option(()=> Char(' ') ) );
+ }
+ public bool SpacedLBracket() /*SpacedLBracket: '[' ' '?;*/
+ {
+
+ return And(()=> Char('[') && Option(()=> Char(' ') ) );
+ }
+ public bool SpacedRBracket() /*SpacedRBracket: ' '? ']';*/
+ {
+
+ return And(()=> Option(()=> Char(' ') ) && Char(']') );
+ }
+ public bool ws() /*ws: ' '*;*/
+ {
+
+ return OptRepeat(()=> Char(' ') );
+ }
+ #endregion Grammar Rules
+
+ #region Optimization Data
+ internal static OptimizedCharset optimizedCharset0;
+ internal static OptimizedCharset optimizedCharset1;
+
+ internal static OptimizedLiterals optimizedLiterals0;
+
+ static ExcelFormula()
+ {
+ {
+ OptimizedCharset.Range[] ranges = new OptimizedCharset.Range[]
+ {new OptimizedCharset.Range('A','Z'),
+ new OptimizedCharset.Range('a','z'),
+ new OptimizedCharset.Range('0','9'),
+ new OptimizedCharset.Range(',','.'),
+ };
+ char[] oneOfChars = new char[] {'!','"','#','$','%'
+ ,'&','(',')','+',';'
+ ,'<','=','>','@','^'
+ ,'_','`','{','|','}'
+ ,'~',' '};
+ optimizedCharset0= new OptimizedCharset(ranges,oneOfChars);
+ }
+
+ {
+ OptimizedCharset.Range[] ranges = new OptimizedCharset.Range[]
+ {new OptimizedCharset.Range('A','Z'),
+ new OptimizedCharset.Range('a','z'),
+ new OptimizedCharset.Range('0','9'),
+ new OptimizedCharset.Range(',','.'),
+ };
+ char[] oneOfChars = new char[] {'!','"','#','$','%'
+ ,'&','(',')','*','+'
+ ,'/',':',';','<','='
+ ,'>','?','@','\\','^'
+ ,'_','`','{','|','}'
+ ,'~'};
+ optimizedCharset1= new OptimizedCharset(ranges,oneOfChars);
+ }
+
+
+ {
+ string[] literals=
+ { "<>",">=","<=","^","*","/","+","-",
+ "&","=","<",">" };
+ optimizedLiterals0= new OptimizedLiterals(literals);
+ }
+
+
+ }
+ #endregion Optimization Data
+ }
+}
diff --git a/OpenXmlPowerTools/FieldRetriever.cs b/OpenXmlPowerTools/FieldRetriever.cs
new file mode 100644
index 0000000..8992bd6
--- /dev/null
+++ b/OpenXmlPowerTools/FieldRetriever.cs
@@ -0,0 +1,431 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class FieldRetriever
+ {
+ public static string InstrText(XElement root, int id)
+ {
+
+ XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
+
+#if false
+ // This is the old code. Both versions work - the caching version is significantly faster.
+ var relevantElements = root.Descendants()
+ .Where(e =>
+ {
+ Stack<FieldElementTypeInfo> s = e.Annotation<Stack<FieldElementTypeInfo>>();
+ if (s != null)
+ return s.Any(z => z.Id == id &&
+ z.FieldElementType == FieldElementTypeEnum.InstrText);
+ return false;
+ })
+ .ToList();
+#else
+ var cachedAnnotationInformation = root.Annotation<Dictionary<int, List<XElement>>>();
+ if (cachedAnnotationInformation == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ // it is possible that a field code contains no instr text
+ if (!cachedAnnotationInformation.ContainsKey(id))
+ return "";
+ var relevantElements = cachedAnnotationInformation[id];
+#endif
+
+ var groupedSubFields = relevantElements
+ .GroupAdjacent(e =>
+ {
+ Stack<FieldElementTypeInfo> s = e.Annotation<Stack<FieldElementTypeInfo>>();
+ var stackElement = s.FirstOrDefault(z => z.Id == id);
+ var elementsBefore = s.TakeWhile(z => z != stackElement);
+ return elementsBefore.Any();
+ })
+ .ToList();
+
+ var instrText = groupedSubFields
+ .Select(g =>
+ {
+ if (g.Key == false)
+ {
+ return g.Select(e =>
+ {
+ Stack<FieldElementTypeInfo> s = e.Annotation<Stack<FieldElementTypeInfo>>();
+ var stackElement = s.FirstOrDefault(z => z.Id == id);
+ if (stackElement.FieldElementType == FieldElementTypeEnum.InstrText &&
+ e.Name == w + "instrText")
+ return e.Value;
+ return "";
+ })
+ .StringConcatenate();
+ }
+ else
+ {
+ Stack<FieldElementTypeInfo> s = g.First().Annotation<Stack<FieldElementTypeInfo>>();
+ var stackElement = s.FirstOrDefault(z => z.Id == id);
+ var elementBefore = s.TakeWhile(z => z != stackElement).Last();
+ var subFieldId = elementBefore.Id;
+ return InstrText(root, subFieldId);
+ }
+ })
+ .StringConcatenate();
+
+ return "{" + instrText + "}";
+ }
+
+ public static void AnnotateWithFieldInfo(OpenXmlPart part)
+ {
+ XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
+
+ XElement root = part.GetXDocument().Root;
+ var r = root.DescendantsAndSelf()
+ .Rollup(
+ new FieldElementTypeStack
+ {
+ Id = 0,
+ FiStack = null,
+ },
+ (e, s) =>
+ {
+ if (e.Name == w + "fldChar")
+ {
+ if (e.Attribute(w + "fldCharType").Value == "begin")
+ {
+ Stack<FieldElementTypeInfo> fis;
+ if (s.FiStack == null)
+ fis = new Stack<FieldElementTypeInfo>();
+ else
+ fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse());
+ fis.Push(
+ new FieldElementTypeInfo
+ {
+ Id = s.Id + 1,
+ FieldElementType = FieldElementTypeEnum.Begin,
+ });
+ return new FieldElementTypeStack
+ {
+ Id = s.Id + 1,
+ FiStack = fis,
+ };
+ };
+ if (e.Attribute(w + "fldCharType").Value == "separate")
+ {
+ Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse());
+ FieldElementTypeInfo wfi = fis.Pop();
+ fis.Push(
+ new FieldElementTypeInfo
+ {
+ Id = wfi.Id,
+ FieldElementType = FieldElementTypeEnum.Separate,
+ });
+ return new FieldElementTypeStack
+ {
+ Id = s.Id,
+ FiStack = fis,
+ };
+ }
+ if (e.Attribute(w + "fldCharType").Value == "end")
+ {
+ Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse());
+ FieldElementTypeInfo wfi = fis.Pop();
+ return new FieldElementTypeStack
+ {
+ Id = s.Id,
+ FiStack = fis,
+ };
+ }
+ }
+ if (s.FiStack == null || s.FiStack.Count() == 0)
+ return s;
+ FieldElementTypeInfo wfi3 = s.FiStack.Peek();
+ if (wfi3.FieldElementType == FieldElementTypeEnum.Begin)
+ {
+ Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse());
+ FieldElementTypeInfo wfi2 = fis.Pop();
+ fis.Push(
+ new FieldElementTypeInfo
+ {
+ Id = wfi2.Id,
+ FieldElementType = FieldElementTypeEnum.InstrText,
+ });
+ return new FieldElementTypeStack
+ {
+ Id = s.Id,
+ FiStack = fis,
+ };
+ }
+ if (wfi3.FieldElementType == FieldElementTypeEnum.Separate)
+ {
+ Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse());
+ FieldElementTypeInfo wfi2 = fis.Pop();
+ fis.Push(
+ new FieldElementTypeInfo
+ {
+ Id = wfi2.Id,
+ FieldElementType = FieldElementTypeEnum.Result,
+ });
+ return new FieldElementTypeStack
+ {
+ Id = s.Id,
+ FiStack = fis,
+ };
+ }
+ if (wfi3.FieldElementType == FieldElementTypeEnum.End)
+ {
+ Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse());
+ fis.Pop();
+ if (!fis.Any())
+ fis = null;
+ return new FieldElementTypeStack
+ {
+ Id = s.Id,
+ FiStack = fis,
+ };
+ }
+ return s;
+ });
+ var elementPlusInfo = root.DescendantsAndSelf().PtZip(r, (t1, t2) =>
+ {
+ return new
+ {
+ Element = t1,
+ Id = t2.Id,
+ WmlFieldInfoStack = t2.FiStack,
+ };
+ });
+ foreach (var item in elementPlusInfo)
+ {
+ if (item.WmlFieldInfoStack != null)
+ item.Element.AddAnnotation(item.WmlFieldInfoStack);
+ }
+
+ //This code is useful when you want to take a look at the annotations, making sure that they are made correctly.
+ //
+ //foreach (var desc in root.DescendantsAndSelf())
+ //{
+ // Stack<FieldElementTypeInfo> s = desc.Annotation<Stack<FieldElementTypeInfo>>();
+ // if (s != null)
+ // {
+ // Console.WriteLine(desc.Name.LocalName.PadRight(20));
+ // foreach (var item in s)
+ // {
+ // Console.WriteLine(" {0:0000} {1}", item.Id, item.FieldElementType.ToString());
+ // Console.ReadKey();
+ // }
+ // }
+ //}
+
+ var cachedAnnotationInformation = new Dictionary<int, List<XElement>>();
+ foreach (var desc in root.DescendantsTrimmed(d => d.Name == W.rPr || d.Name == W.pPr))
+ {
+ Stack<FieldElementTypeInfo> s = desc.Annotation<Stack<FieldElementTypeInfo>>();
+
+ if (s != null )
+ {
+ foreach (var item in s)
+ {
+ if (item.FieldElementType == FieldElementTypeEnum.InstrText)
+ {
+ if (cachedAnnotationInformation.ContainsKey(item.Id))
+ {
+ cachedAnnotationInformation[item.Id].Add(desc);
+ }
+ else
+ {
+ cachedAnnotationInformation.Add(item.Id, new List<XElement>() { desc });
+ }
+ }
+ }
+ }
+ }
+ root.AddAnnotation(cachedAnnotationInformation);
+ }
+
+ private enum State
+ {
+ InToken,
+ InWhiteSpace,
+ InQuotedToken,
+ OnOpeningQuote,
+ OnClosingQuote,
+ OnBackslash,
+ }
+
+ private static string[] GetTokens(string field)
+ {
+ State state = State.InWhiteSpace;
+ int tokenStart = 0;
+ char quoteStart = char.MinValue;
+ List<string> tokens = new List<string>();
+ for (int c = 0; c < field.Length; c++)
+ {
+ if (Char.IsWhiteSpace(field[c]))
+ {
+ if (state == State.InToken)
+ {
+ tokens.Add(field.Substring(tokenStart, c - tokenStart));
+ state = State.InWhiteSpace;
+ continue;
+ }
+ if (state == State.OnOpeningQuote)
+ {
+ tokenStart = c;
+ state = State.InQuotedToken;
+ }
+ if (state == State.OnClosingQuote)
+ state = State.InWhiteSpace;
+ continue;
+ }
+ if (field[c] == '\\')
+ {
+ if (state == State.InQuotedToken)
+ {
+ state = State.OnBackslash;
+ continue;
+ }
+ }
+ if (state == State.OnBackslash)
+ {
+ state = State.InQuotedToken;
+ continue;
+ }
+ if (field[c] == '"' || field[c] == '\'' || field[c] == 0x201d)
+ {
+ if (state == State.InWhiteSpace)
+ {
+ quoteStart = field[c];
+ state = State.OnOpeningQuote;
+ continue;
+ }
+ if (state == State.InQuotedToken)
+ {
+ if (field[c] == quoteStart)
+ {
+ tokens.Add(field.Substring(tokenStart, c - tokenStart));
+ state = State.OnClosingQuote;
+ continue;
+ }
+ continue;
+ }
+ if (state == State.OnOpeningQuote)
+ {
+ if (field[c] == quoteStart)
+ {
+ state = State.OnClosingQuote;
+ continue;
+ }
+ else
+ {
+ tokenStart = c;
+ state = State.InQuotedToken;
+ continue;
+ }
+ }
+ continue;
+ }
+ if (state == State.InWhiteSpace)
+ {
+ tokenStart = c;
+ state = State.InToken;
+ continue;
+ }
+ if (state == State.OnOpeningQuote)
+ {
+ tokenStart = c;
+ state = State.InQuotedToken;
+ continue;
+ }
+ if (state == State.OnClosingQuote)
+ {
+ tokenStart = c;
+ state = State.InToken;
+ continue;
+ }
+ }
+ if (state == State.InToken)
+ tokens.Add(field.Substring(tokenStart, field.Length - tokenStart));
+ return tokens.ToArray();
+ }
+
+ public static FieldInfo ParseField(string field)
+ {
+ FieldInfo emptyField = new FieldInfo
+ {
+ FieldType = "",
+ Arguments = new string[] { },
+ Switches = new string[] { },
+ };
+
+ if (field.Length == 0)
+ return emptyField;
+ string fieldType = field.TrimStart().Split(' ').FirstOrDefault();
+ if (fieldType == null)
+ return emptyField;
+ if (fieldType.ToUpper() != "HYPERLINK" &&
+ fieldType.ToUpper() != "REF" &&
+ fieldType.ToUpper() != "SEQ" &&
+ fieldType.ToUpper() != "STYLEREF")
+ return emptyField;
+ string[] tokens = GetTokens(field);
+ if (tokens.Length == 0)
+ return emptyField;
+ FieldInfo fieldInfo = new FieldInfo()
+ {
+ FieldType = tokens[0],
+ Switches = tokens.Where(t => t[0] == '\\').ToArray(),
+ Arguments = tokens.Skip(1).Where(t => t[0] != '\\').ToArray(),
+ };
+ return fieldInfo;
+ }
+
+ public class FieldInfo
+ {
+ public string FieldType;
+ public string[] Switches;
+ public string[] Arguments;
+ }
+
+ public enum FieldElementTypeEnum
+ {
+ Begin,
+ InstrText,
+ Separate,
+ Result,
+ End,
+ };
+
+ public class FieldElementTypeInfo
+ {
+ public int Id;
+ public FieldElementTypeEnum FieldElementType;
+ }
+
+ public class FieldElementTypeStack
+ {
+ public int Id;
+ public Stack<FieldElementTypeInfo> FiStack;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/FormattingAssembler.cs b/OpenXmlPowerTools/FormattingAssembler.cs
new file mode 100644
index 0000000..a638e43
--- /dev/null
+++ b/OpenXmlPowerTools/FormattingAssembler.cs
@@ -0,0 +1,3525 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.Drawing;
+
+namespace OpenXmlPowerTools
+{
+ public class FormattingAssemblerSettings
+ {
+ public bool RemoveStyleNamesFromParagraphAndRunProperties;
+ public bool ClearStyles;
+ public bool OrderElementsPerStandard;
+ public bool CreateHtmlConverterAnnotationAttributes;
+ public bool RestrictToSupportedNumberingFormats;
+ public bool RestrictToSupportedLanguages;
+ public ListItemRetrieverSettings ListItemRetrieverSettings;
+
+ public FormattingAssemblerSettings()
+ {
+ RemoveStyleNamesFromParagraphAndRunProperties = true;
+ ClearStyles = true;
+ OrderElementsPerStandard = true;
+ CreateHtmlConverterAnnotationAttributes = true;
+ RestrictToSupportedNumberingFormats = false;
+ RestrictToSupportedLanguages = false;
+ ListItemRetrieverSettings = new ListItemRetrieverSettings();
+ }
+ }
+
+ public static class FormattingAssembler
+ {
+ public static WmlDocument AssembleFormatting(WmlDocument document, FormattingAssemblerSettings settings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ AssembleFormatting(doc, settings);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void AssembleFormatting(WordprocessingDocument wDoc, FormattingAssemblerSettings settings)
+ {
+ FormattingAssemblerInfo fai = new FormattingAssemblerInfo();
+ XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ XElement defaultParagraphStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true &&
+ (string)st.Attribute(W.type) == "paragraph");
+ if (defaultParagraphStyle != null)
+ fai.DefaultParagraphStyleName = (string)defaultParagraphStyle.Attribute(W.styleId);
+ XElement defaultCharacterStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true &&
+ (string)st.Attribute(W.type) == "character");
+ if (defaultCharacterStyle != null)
+ fai.DefaultCharacterStyleName = (string)defaultCharacterStyle.Attribute(W.styleId);
+ XElement defaultTableStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true &&
+ (string)st.Attribute(W.type) == "table");
+ if (defaultTableStyle != null)
+ fai.DefaultTableStyleName = (string)defaultTableStyle.Attribute(W.styleId);
+ ListItemRetrieverSettings listItemRetrieverSettings = new ListItemRetrieverSettings();
+ AssembleListItemInformation(wDoc, settings.ListItemRetrieverSettings);
+ foreach (var part in wDoc.ContentParts())
+ {
+ var pxd = part.GetXDocument();
+ FixNonconformantHexValues(pxd.Root);
+ AnnotateWithGlobalDefaults(wDoc, pxd.Root, settings);
+ AnnotateTablesWithTableStyles(wDoc, pxd.Root);
+ AnnotateParagraphs(fai, wDoc, pxd.Root, settings);
+ AnnotateRuns(fai, wDoc, pxd.Root, settings);
+ }
+ NormalizeListItems(fai, wDoc, settings);
+ if (settings.ClearStyles)
+ ClearStyles(wDoc);
+ foreach (var part in wDoc.ContentParts())
+ {
+ var pxd = part.GetXDocument();
+ pxd.Root.Descendants().Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
+ FormattingAssembler.NormalizePropsForPart(pxd, settings);
+ var newRoot = (XElement)CleanupTransform(pxd.Root);
+ pxd.Root.ReplaceWith(newRoot);
+ part.PutXDocument();
+ }
+ }
+
+ private static void FixNonconformantHexValues(XElement root)
+ {
+ foreach (var tblLook in root.Descendants(W.tblLook))
+ {
+ if (tblLook.Attributes().Any(a => a.Name != W.val))
+ continue;
+ if (tblLook.Attribute(W.val) == null)
+ continue;
+ string hexValue = tblLook.Attribute(W.val).Value;
+ int val = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber);
+ tblLook.Add(new XAttribute(W.firstRow, (val & 0x0020) != 0 ? "1" : "0"));
+ tblLook.Add(new XAttribute(W.lastRow, (val & 0x0040) != 0 ? "1" : "0"));
+ tblLook.Add(new XAttribute(W.firstColumn, (val & 0x0080) != 0 ? "1" : "0"));
+ tblLook.Add(new XAttribute(W.lastColumn, (val & 0x0100) != 0 ? "1" : "0"));
+ tblLook.Add(new XAttribute(W.noHBand, (val & 0x0200) != 0 ? "1" : "0"));
+ tblLook.Add(new XAttribute(W.noVBand, (val & 0x0400) != 0 ? "1" : "0"));
+ }
+ foreach (var cnfStyle in root.Descendants(W.cnfStyle))
+ {
+ if (cnfStyle.Attributes().Any(a => a.Name != W.val))
+ continue;
+ if (cnfStyle.Attribute(W.val) == null)
+ continue;
+ var va = cnfStyle.Attribute(W.val).Value.ToArray();
+ cnfStyle.Add(new XAttribute(W.firstRow, va[0]));
+ cnfStyle.Add(new XAttribute(W.lastRow, va[1]));
+ cnfStyle.Add(new XAttribute(W.firstColumn, va[2]));
+ cnfStyle.Add(new XAttribute(W.lastColumn, va[3]));
+ cnfStyle.Add(new XAttribute(W.oddVBand, va[4]));
+ cnfStyle.Add(new XAttribute(W.evenVBand, va[5]));
+ cnfStyle.Add(new XAttribute(W.oddHBand, va[6]));
+ cnfStyle.Add(new XAttribute(W.evenHBand, va[7]));
+ cnfStyle.Add(new XAttribute(W.firstRowLastColumn, va[8]));
+ cnfStyle.Add(new XAttribute(W.firstRowFirstColumn, va[9]));
+ cnfStyle.Add(new XAttribute(W.lastRowLastColumn, va[10]));
+ cnfStyle.Add(new XAttribute(W.lastRowFirstColumn, va[11]));
+ }
+ }
+
+ private static object CleanupTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.tabs && element.Element(W.tab) == null)
+ return null;
+
+ if (element.Name == W.tblStyleRowBandSize || element.Name == W.tblStyleColBandSize)
+ return null;
+
+ // a cleaner solution would be to not include the w:ins and w:del elements when rolling up the paragraph run properties into
+ // the run properties.
+ if ((element.Name == W.ins || element.Name == W.del) && element.Parent.Name == W.rPr)
+ {
+ if (element.Parent.Parent.Name == W.r || element.Parent.Parent.Name == W.rPrChange)
+ return null;
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => CleanupTransform(n)));
+ }
+ return node;
+ }
+
+ private static void ClearStyles(WordprocessingDocument wDoc)
+ {
+ var stylePart = wDoc.MainDocumentPart.StyleDefinitionsPart;
+ var sXDoc = stylePart.GetXDocument();
+
+ var newRoot = new XElement(sXDoc.Root.Name,
+ sXDoc.Root.Attributes(),
+ sXDoc.Root.Elements().Select(e =>
+ {
+ if (e.Name != W.style)
+ return e;
+ return new XElement(e.Name,
+ e.Attributes(),
+ e.Element(W.name),
+ new XElement(W.pPr),
+ new XElement(W.rPr));
+ }));
+
+ var globalrPr = newRoot
+ .Elements(W.docDefaults)
+ .Elements(W.rPrDefault)
+ .Elements(W.rPr)
+ .FirstOrDefault();
+ if (globalrPr != null)
+ globalrPr.ReplaceWith(new XElement(W.rPr));
+
+ var globalpPr = newRoot
+ .Elements(W.docDefaults)
+ .Elements(W.pPrDefault)
+ .Elements(W.pPr)
+ .FirstOrDefault();
+ if (globalpPr != null)
+ globalpPr.ReplaceWith(new XElement(W.pPr));
+
+ sXDoc.Root.ReplaceWith(newRoot);
+
+ stylePart.PutXDocument();
+ }
+
+ private static void NormalizeListItems(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, FormattingAssemblerSettings settings)
+ {
+ foreach (var part in wDoc.ContentParts())
+ {
+ var pxd = part.GetXDocument();
+ XElement newRoot = (XElement)NormalizeListItemsTransform(fai, wDoc, pxd.Root, settings);
+ if (newRoot.Attribute(XNamespace.Xmlns + "pt14") == null)
+ newRoot.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName));
+ if (newRoot.Attribute(XNamespace.Xmlns + "mc") == null)
+ newRoot.Add(new XAttribute(XNamespace.Xmlns + "mc", MC.mc.NamespaceName));
+ pxd.Root.ReplaceWith(newRoot);
+ }
+ }
+
+ private static object NormalizeListItemsTransform(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XNode node, FormattingAssemblerSettings settings)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ {
+ var li = ListItemRetriever.RetrieveListItem(wDoc, element, settings.ListItemRetrieverSettings);
+ if (li != null)
+ {
+ ListItemRetriever.ListItemInfo listItemInfo = element.Annotation<ListItemRetriever.ListItemInfo>();
+
+ var newParaProps = new XElement(W.pPr,
+ element.Elements(W.pPr).Elements().Where(e => e.Name != W.numPr)
+ );
+
+ XElement listItemRunProps = null;
+ int? abstractNumId = null;
+ if (listItemInfo != null)
+ {
+ abstractNumId = listItemInfo.AbstractNumId;
+
+ var paraStyleRunProps = CharStyleRollup(fai, wDoc, element);
+
+ var paragraphStyleName = (string)element
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ string defaultStyleName = (string)wDoc
+ .MainDocumentPart
+ .StyleDefinitionsPart
+ .GetXDocument()
+ .Root
+ .Elements(W.style)
+ .Where(s => (string)s.Attribute(W.type) == "paragraph" && s.Attribute(W._default).ToBoolean() == true)
+ .Attributes(W.styleId)
+ .FirstOrDefault();
+
+ if (paragraphStyleName == null)
+ paragraphStyleName = defaultStyleName;
+
+ XDocument stylesXDoc = wDoc
+ .MainDocumentPart
+ .StyleDefinitionsPart
+ .GetXDocument();
+
+ // put together run props for list item.
+
+ XElement lvlStyleRpr = ParaStyleRunPropsStack(wDoc, paragraphStyleName)
+ .Aggregate(new XElement(W.rPr),
+ (r, s) =>
+ {
+ var newCharStyleRunProps = MergeStyleElement(s, r);
+ return newCharStyleRunProps;
+ });
+
+ var mergedRunProps = MergeStyleElement(lvlStyleRpr, paraStyleRunProps);
+
+ var accumulatedRunProps = element.Elements(PtOpenXml.pPr).Elements(W.rPr).FirstOrDefault();
+ if (accumulatedRunProps != null)
+ mergedRunProps = MergeStyleElement(accumulatedRunProps, mergedRunProps);
+
+ var listItemLvl = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(element));
+ var listItemLvlRunProps = listItemLvl.Elements(W.rPr).FirstOrDefault();
+ listItemRunProps = MergeStyleElement(listItemLvlRunProps, mergedRunProps);
+
+ if ((string)listItemLvl.Elements(W.numFmt).Attributes(W.val).FirstOrDefault() == "bullet")
+ {
+ listItemRunProps.Elements(W.rtl).Remove();
+ }
+ else
+ {
+ var pPr = element.Element(PtOpenXml.pPr);
+ if (pPr != null)
+ {
+ XElement bidiel = pPr.Element(W.bidi);
+ bool bidi = bidiel != null && (bidiel.Attribute(W.val) == null || bidiel.Attribute(W.val).ToBoolean() == true);
+ if (bidi)
+ {
+ listItemRunProps = MergeStyleElement(new XElement(W.rPr,
+ new XElement(W.rtl)), listItemRunProps);
+ }
+ }
+ }
+ }
+
+ var paragraphLevel = ListItemRetriever.GetParagraphLevel(element);
+ ListItemRetriever.LevelNumbers levelNums = element.Annotation<ListItemRetriever.LevelNumbers>();
+ string levelNumsString = levelNums
+ .LevelNumbersArray
+ .Take(paragraphLevel + 1)
+ .Select(i => i.ToString() + ".")
+ .StringConcatenate()
+ .TrimEnd('.');
+
+ var listItemRun = new XElement(W.r,
+ new XAttribute(PtOpenXml.ListItemRun, levelNumsString),
+ element.Attribute(PtOpenXml.FontName),
+ element.Attribute(PtOpenXml.LanguageType),
+ listItemRunProps,
+ new XElement(W.t,
+ new XAttribute(XNamespace.Xml + "space", "preserve"),
+ li));
+
+ AdjustFontAttributes(wDoc, listItemRun, null, listItemRunProps, settings);
+
+ var lvl = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(element));
+ XElement suffix = new XElement(W.tab);
+ var su = (string)lvl.Elements(W.suff).Attributes(W.val).FirstOrDefault();
+ if (su == "space")
+ suffix = new XElement(W.t,
+ new XAttribute(XNamespace.Xml + "space", "preserve"),
+ " ");
+ else if (su == "nothing")
+ suffix = null;
+
+ var jc = (string)lvl.Elements(W.lvlJc).Attributes(W.val).FirstOrDefault();
+ if (jc == "right")
+ {
+ var accumulatedParaProps = element.Element(PtOpenXml.pPr);
+
+ var hangingAtt = accumulatedParaProps.Elements(W.ind).Attributes(W.hanging).FirstOrDefault();
+ if (hangingAtt == null)
+ {
+ var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun);
+ var ind = accumulatedParaProps.Element(W.ind);
+ if (ind == null)
+ {
+ ind = new XElement(W.ind);
+ accumulatedParaProps.Add(ind);
+ }
+ ind.Add(new XAttribute(W.hanging, listItemRunLength.ToString()));
+ }
+ else
+ {
+ var hanging = (int)hangingAtt;
+ var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun);
+ hanging += listItemRunLength; // should be width of list item, in twips
+ hangingAtt.Value = hanging.ToString();
+ }
+ }
+ else if (jc == "center")
+ {
+ var accumulatedParaProps = element.Element(PtOpenXml.pPr);
+
+ var hangingAtt = accumulatedParaProps.Elements(W.ind).Attributes(W.hanging).FirstOrDefault();
+ if (hangingAtt == null)
+ {
+ var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun);
+ var ind = accumulatedParaProps.Element(W.ind);
+ if (ind == null)
+ {
+ ind = new XElement(W.ind);
+ accumulatedParaProps.Add(ind);
+ }
+ ind.Add(new XAttribute(W.hanging, (listItemRunLength / 2).ToString()));
+ }
+ else
+ {
+ var hanging = (int)hangingAtt;
+ var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun);
+ hanging += (listItemRunLength / 2); // should be half of width of list item, in twips
+ hangingAtt.Value = hanging.ToString();
+ }
+ }
+ AddTabAtLeftIndent(element.Element(PtOpenXml.pPr));
+
+ XElement newPara = new XElement(W.p,
+ element.Attribute(PtOpenXml.FontName),
+ element.Attribute(PtOpenXml.LanguageType),
+ element.Attribute(PtOpenXml.Unid),
+ new XAttribute(PtOpenXml.AbstractNumId, abstractNumId),
+ newParaProps,
+ listItemRun,
+ suffix != null ?
+ new XElement(W.r,
+ new XAttribute(PtOpenXml.ListItemRun, levelNumsString),
+ listItemRunProps,
+ suffix) : null,
+ element.Elements().Where(e => e.Name != W.pPr).Select(n => NormalizeListItemsTransform(fai, wDoc, n, settings)));
+ return newPara;
+
+ }
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => NormalizeListItemsTransform(fai, wDoc, n, settings)));
+ }
+ return node;
+ }
+
+ private static void AddTabAtLeftIndent(XElement pPr)
+ {
+ int left = 0;
+ var ind = pPr.Element(W.ind);
+
+ // todo need to handle W.start
+ if (pPr.Attribute(W.left) != null)
+ left = (int)pPr.Attribute(W.left);
+ var tabs = pPr.Element(W.tabs);
+ if (tabs == null)
+ {
+ tabs = new XElement(W.tabs);
+ pPr.Add(tabs);
+ }
+ var tabAtLeft = tabs.Elements(W.tab).FirstOrDefault(t => (int)t.Attribute(W.pos) == left);
+ if (tabAtLeft == null)
+ {
+ tabs.Add(
+ new XElement(W.tab,
+ new XAttribute(W.val, "left"),
+ new XAttribute(W.pos, left)));
+ }
+ }
+
+ public static XName[] PtNamesToKeep = new[] {
+ PtOpenXml.FontName,
+ PtOpenXml.AbstractNumId,
+ PtOpenXml.StyleName,
+ PtOpenXml.LanguageType,
+ PtOpenXml.ListItemRun,
+ PtOpenXml.Unid,
+ };
+
+ public static void NormalizePropsForPart(XDocument pxd, FormattingAssemblerSettings settings)
+ {
+ if (settings.CreateHtmlConverterAnnotationAttributes)
+ {
+ pxd.Root.Descendants().Attributes().Where(d => d.Name.Namespace == PtOpenXml.pt &&
+ !PtNamesToKeep.Contains(d.Name)).Remove();
+ if (pxd.Root.Attribute(XNamespace.Xmlns + "pt14") == null)
+ pxd.Root.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName));
+ if (pxd.Root.Attribute(XNamespace.Xmlns + "mc") == null)
+ pxd.Root.Add(new XAttribute(XNamespace.Xmlns + "mc", MC.mc.NamespaceName));
+ XAttribute mci = pxd.Root.Attribute(MC.Ignorable);
+ if (mci != null)
+ {
+ if (!pxd.Root.Attribute(MC.Ignorable).Value.Contains("pt14"))
+ {
+ var ig = pxd.Root.Attribute(MC.Ignorable).Value + " pt14";
+ mci.Value = ig;
+ }
+ }
+ else
+ {
+ pxd.Root.Add(new XAttribute(MC.Ignorable, "pt14"));
+ }
+ }
+ else
+ {
+ pxd.Root.Descendants().Attributes().Where(d => d.Name.Namespace == PtOpenXml.pt).Remove();
+ }
+ var runProps = pxd.Root.Descendants(PtOpenXml.rPr).ToList();
+ foreach (var item in runProps)
+ {
+ XElement newRunProps = new XElement(W.rPr,
+ item.Attributes(),
+ item.Elements());
+ XElement parent = item.Parent;
+ if (parent.Name == W.p)
+ {
+ XElement existingParaProps = parent.Element(W.pPr);
+ if (existingParaProps == null)
+ {
+ existingParaProps = new XElement(W.pPr);
+ parent.Add(existingParaProps);
+ }
+ XElement existingRunProps = existingParaProps.Element(W.rPr);
+ if (existingRunProps != null)
+ {
+ if (!settings.RemoveStyleNamesFromParagraphAndRunProperties)
+ {
+ if (newRunProps.Element(W.rStyle) == null)
+ newRunProps.Add(existingRunProps.Element(W.rStyle));
+ }
+ existingRunProps.ReplaceWith(newRunProps);
+ }
+ else
+ existingParaProps.Add(newRunProps);
+ }
+ else
+ {
+ XElement existingRunProps = parent.Element(W.rPr);
+ if (existingRunProps != null)
+ {
+ if (!settings.RemoveStyleNamesFromParagraphAndRunProperties)
+ {
+ if (newRunProps.Element(W.rStyle) == null)
+ newRunProps.Add(existingRunProps.Element(W.rStyle));
+ }
+ existingRunProps.ReplaceWith(newRunProps);
+ }
+ else
+ parent.Add(newRunProps);
+ }
+ }
+ var paraProps = pxd.Root.Descendants(PtOpenXml.pPr).ToList();
+ foreach (var item in paraProps)
+ {
+ var paraRunProps = item.Parent.Elements(W.pPr).Elements(W.rPr).FirstOrDefault();
+ var merged = MergeStyleElement(item.Element(W.rPr), paraRunProps);
+ if (!settings.RemoveStyleNamesFromParagraphAndRunProperties)
+ {
+ if (merged.Element(W.rStyle) == null)
+ {
+ merged.Add(paraRunProps.Element(W.rStyle));
+ }
+ }
+
+ XElement newParaProps = new XElement(W.pPr,
+ item.Attributes(),
+ item.Elements().Where(e => e.Name != W.rPr),
+ merged);
+ XElement para = item.Parent;
+ XElement existingParaProps = para.Element(W.pPr);
+ if (existingParaProps != null)
+ {
+ if (!settings.RemoveStyleNamesFromParagraphAndRunProperties)
+ {
+ if (newParaProps.Element(W.pStyle) == null)
+ newParaProps.Add(existingParaProps.Element(W.pStyle));
+ }
+ existingParaProps.ReplaceWith(newParaProps);
+ }
+ else
+ para.Add(newParaProps);
+ }
+ var tblProps = pxd.Root.Descendants(PtOpenXml.tblPr).ToList();
+ foreach (var item in tblProps)
+ {
+ XElement newTblProps = new XElement(item);
+ newTblProps.Name = W.tblPr;
+ XElement table = item.Parent;
+ XElement existingTableProps = table.Element(W.tblPr);
+ if (existingTableProps != null)
+ existingTableProps.ReplaceWith(newTblProps);
+ else
+ table.AddFirst(newTblProps);
+ }
+ var trProps = pxd.Root.Descendants(PtOpenXml.trPr).ToList();
+ foreach (var item in trProps)
+ {
+ XElement newTrProps = new XElement(item);
+ newTrProps.Name = W.trPr;
+ XElement row = item.Parent;
+ XElement existingRowProps = row.Element(W.trPr);
+ if (existingRowProps != null)
+ existingRowProps.ReplaceWith(newTrProps);
+ else
+ row.AddFirst(newTrProps);
+ }
+ var tcProps = pxd.Root.Descendants(PtOpenXml.tcPr).ToList();
+ foreach (var item in tcProps)
+ {
+ XElement newTcProps = new XElement(item);
+ newTcProps.Name = W.tcPr;
+ XElement row = item.Parent;
+ XElement existingRowProps = row.Element(W.tcPr);
+ if (existingRowProps != null)
+ existingRowProps.ReplaceWith(newTcProps);
+ else
+ row.AddFirst(newTcProps);
+ }
+ pxd.Root.Descendants(W.numPr).Remove();
+ if (settings.RemoveStyleNamesFromParagraphAndRunProperties)
+ {
+ pxd.Root.Descendants(W.pStyle).Where(ps => ps.Parent.Name == W.pPr).Remove();
+ pxd.Root.Descendants(W.rStyle).Where(ps => ps.Parent.Name == W.rPr).Remove();
+ }
+ pxd.Root.Descendants(W.tblStyle).Where(ps => ps.Parent.Name == W.tblPr).Remove();
+ pxd.Root.Descendants().Where(d => d.Name.Namespace == PtOpenXml.pt).Remove();
+ if (settings.OrderElementsPerStandard)
+ {
+ XElement newRoot = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(pxd.Root);
+ pxd.Root.ReplaceWith(newRoot);
+ }
+ }
+
+ private static void AssembleListItemInformation(WordprocessingDocument wordDoc, ListItemRetrieverSettings settings)
+ {
+ foreach (var part in wordDoc.ContentParts())
+ {
+ XDocument xDoc = part.GetXDocument();
+ foreach (var para in xDoc.Descendants(W.p))
+ {
+ ListItemRetriever.RetrieveListItem(wordDoc, para, settings);
+ }
+ }
+ }
+
+ private static void AnnotateWithGlobalDefaults(WordprocessingDocument wDoc, XElement rootElement, FormattingAssemblerSettings settings)
+ {
+ XElement globalDefaultParaProps = null;
+ XElement globalDefaultParaPropsAsDefined = null;
+ XElement globalDefaultRunProps = null;
+ XElement globalDefaultRunPropsAsDefined = null;
+ XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ var defaultParaStyleName = (string)sXDoc
+ .Root
+ .Elements(W.style)
+ .Where(st => (string)st.Attribute(W.type) == "paragraph" && st.Attribute(W._default).ToBoolean() == true)
+ .Attributes(W.styleId)
+ .FirstOrDefault();
+ var defaultCharStyleName = (string)sXDoc
+ .Root
+ .Elements(W.style)
+ .Where(st => (string)st.Attribute(W.type) == "character" && st.Attribute(W._default).ToBoolean() == true)
+ .Attributes(W.styleId)
+ .FirstOrDefault();
+ XElement docDefaults = sXDoc.Root.Element(W.docDefaults);
+ if (docDefaults != null)
+ {
+ globalDefaultParaPropsAsDefined = docDefaults.Elements(W.pPrDefault).Elements(W.pPr)
+ .FirstOrDefault();
+ if (globalDefaultParaPropsAsDefined == null)
+ globalDefaultParaPropsAsDefined = new XElement(W.pPr,
+ new XElement(W.rPr));
+ globalDefaultRunPropsAsDefined = docDefaults.Elements(W.rPrDefault).Elements(W.rPr)
+ .FirstOrDefault();
+ if (globalDefaultRunPropsAsDefined == null)
+ globalDefaultRunPropsAsDefined = new XElement(W.rPr);
+ if (globalDefaultRunPropsAsDefined.Element(W.rFonts) == null)
+ globalDefaultRunPropsAsDefined.Add(
+ new XElement(W.rFonts,
+ new XAttribute(W.ascii, "Times New Roman"),
+ new XAttribute(W.hAnsi, "Times New Roman"),
+ new XAttribute(W.cs, "Times New Roman")));
+ if (globalDefaultRunPropsAsDefined.Element(W.sz) == null)
+ globalDefaultRunPropsAsDefined.Add(
+ new XElement(W.sz,
+ new XAttribute(W.val, "20")));
+ if (globalDefaultRunPropsAsDefined.Element(W.szCs) == null)
+ globalDefaultRunPropsAsDefined.Add(
+ new XElement(W.szCs,
+ new XAttribute(W.val, "20")));
+
+ var runPropsForGlobalDefaultParaProps = MergeStyleElement(globalDefaultRunPropsAsDefined, globalDefaultParaPropsAsDefined.Element(W.rPr));
+ globalDefaultParaProps = new XElement(globalDefaultParaPropsAsDefined.Name,
+ globalDefaultParaPropsAsDefined.Attributes(),
+ globalDefaultParaPropsAsDefined.Elements().Where(e => e.Name != W.rPr),
+ runPropsForGlobalDefaultParaProps);
+ globalDefaultRunProps = MergeStyleElement(globalDefaultParaPropsAsDefined.Element(W.rPr), globalDefaultRunPropsAsDefined);
+ }
+ var rPr = new XElement(W.rPr,
+ new XElement(W.rFonts,
+ new XAttribute(W.ascii, "Times New Roman"),
+ new XAttribute(W.hAnsi, "Times New Roman"),
+ new XAttribute(W.cs, "Times New Roman")),
+ new XElement(W.sz,
+ new XAttribute(W.val, "20")),
+ new XElement(W.szCs,
+ new XAttribute(W.val, "20")));
+
+ if (globalDefaultParaProps == null)
+ globalDefaultParaProps = new XElement(W.pPr, rPr);
+
+ if (globalDefaultRunProps == null)
+ globalDefaultRunProps = rPr;
+
+ XElement ptGlobalDefaultParaProps = new XElement(globalDefaultParaProps);
+ XElement ptGlobalDefaultRunProps = new XElement(globalDefaultRunProps);
+ ptGlobalDefaultParaProps.Name = PtOpenXml.pPr;
+ ptGlobalDefaultRunProps.Name = PtOpenXml.rPr;
+ var parasAndRuns = rootElement.Descendants().Where(d =>
+ {
+ return d.Name == W.p || d.Name == W.r;
+ });
+ if (settings.CreateHtmlConverterAnnotationAttributes)
+ {
+ foreach (var d in parasAndRuns)
+ {
+ if (d.Name == W.p)
+ {
+ var pStyle = (string)d.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
+ if (pStyle == null)
+ pStyle = defaultParaStyleName;
+ if (pStyle != null)
+ {
+ if (d.Attribute(PtOpenXml.StyleName) != null)
+ d.Attribute(PtOpenXml.StyleName).Value = pStyle;
+ else
+ d.Add(new XAttribute(PtOpenXml.StyleName, pStyle));
+ }
+ d.Add(ptGlobalDefaultParaProps);
+ }
+ else
+ {
+ var rStyle = (string)d.Elements(W.rPr).Elements(W.rStyle).Attributes(W.val).FirstOrDefault();
+ if (rStyle == null)
+ rStyle = defaultCharStyleName;
+ if (rStyle != null)
+ {
+ if (d.Attribute(PtOpenXml.StyleName) != null)
+ d.Attribute(PtOpenXml.StyleName).Value = rStyle;
+ else
+ d.Add(new XAttribute(PtOpenXml.StyleName, rStyle));
+ }
+ d.Add(ptGlobalDefaultRunProps);
+ }
+ }
+ }
+ else
+ {
+ foreach (var d in parasAndRuns)
+ {
+ if (d.Name == W.p)
+ {
+ d.Add(ptGlobalDefaultParaProps);
+ }
+ else
+ {
+ d.Add(ptGlobalDefaultRunProps);
+ }
+ }
+ }
+ }
+
+ private static XElement BlankTcBorders = new XElement(W.tcBorders,
+ new XElement(W.top, new XAttribute(W.val, "nil")),
+ new XElement(W.left, new XAttribute(W.val, "nil")),
+ new XElement(W.bottom, new XAttribute(W.val, "nil")),
+ new XElement(W.right, new XAttribute(W.val, "nil")));
+
+ private static void AnnotateTablesWithTableStyles(WordprocessingDocument wDoc, XElement rootElement)
+ {
+ XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ foreach (var tbl in rootElement.Descendants(W.tbl))
+ {
+ string tblStyleName = (string)tbl.Elements(W.tblPr).Elements(W.tblStyle).Attributes(W.val).FirstOrDefault();
+ if (tblStyleName != null)
+ {
+ XElement style = TableStyleRollup(wDoc, tblStyleName);
+
+ // annotate table with table style, in PowerTools namespace
+ style.Name = PtOpenXml.style;
+ tbl.Add(style);
+
+ // merge tblPr in table style with tblPr of the table
+ // annnotate in PowerTools namespace
+ XElement tblPr2 = style.Element(W.tblPr);
+ XElement tblPr3 = MergeStyleElement(tbl.Element(W.tblPr), tblPr2, true);
+ if (tblPr3 != null)
+ {
+ XElement newTblPr = new XElement(tblPr3);
+ newTblPr.Name = PtOpenXml.pt + "tblPr";
+ tbl.Add(newTblPr);
+ }
+
+ AddTcPrPtToEveryCell(tbl);
+ AddOuterBorders(tbl, style);
+
+ var tableTcPr = style.Element(W.tcPr);
+ if (tableTcPr != null)
+ {
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ bool tcPrPtExists = false;
+ var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr");
+ if (tcPrPt != null)
+ tcPrPtExists = true;
+ else
+ tcPrPt = new XElement(W.tcPr);
+ tcPrPt = MergeStyleElement(tableTcPr, tcPrPt);
+ var newTcPrPt = new XElement(tcPrPt);
+ newTcPrPt.Name = PtOpenXml.tcPr;
+ if (tcPrPtExists)
+ cell.Element(PtOpenXml.tcPr).ReplaceWith(newTcPrPt);
+ else
+ cell.Add(newTcPrPt);
+ }
+ }
+ }
+
+ // Iterate through every row and cell in the table, rolling up row properties and cell properties
+ // as appropriate per the cnfStyle element, then replacing the row and cell properties
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ XElement trPr2 = null;
+ trPr2 = style.Element(W.trPr);
+ if (trPr2 == null)
+ trPr2 = new XElement(W.trPr);
+ XElement rowCnf = row.Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault();
+ if (rowCnf != null)
+ {
+ foreach (var ot in TableStyleOverrideTypes)
+ {
+ XName attName = TableStyleOverrideXNameMap[ot];
+ if (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true)
+ {
+ XElement o = style
+ .Elements(W.tblStylePr)
+ .Where(tsp => (string)tsp.Attribute(W.type) == ot)
+ .FirstOrDefault();
+ if (o != null)
+ {
+ XElement ottrPr = o.Element(W.trPr);
+ trPr2 = MergeStyleElement(ottrPr, trPr2);
+ }
+ }
+ }
+ }
+ trPr2 = MergeStyleElement(row.Element(W.trPr), trPr2);
+ if (trPr2.HasElements)
+ {
+ trPr2.Name = PtOpenXml.pt + "trPr";
+ row.Add(trPr2);
+ }
+ }
+
+ foreach (var ot in TableStyleOverrideTypes)
+ {
+ XName attName = TableStyleOverrideXNameMap[ot];
+ if (attName == W.oddHBand ||
+ attName == W.evenHBand ||
+ attName == W.firstRow ||
+ attName == W.lastRow)
+ {
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ XElement rowCnf = row.Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault();
+ if (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true)
+ {
+ XElement o = style
+ .Elements(W.tblStylePr)
+ .Where(tsp => (string)tsp.Attribute(W.type) == ot)
+ .FirstOrDefault();
+ if (o != null)
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ bool tcPrPtExists = false;
+ var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr");
+ if (tcPrPt != null)
+ tcPrPtExists = true;
+ else
+ tcPrPt = new XElement(W.tcPr);
+ tcPrPt = MergeStyleElement(o.Element(W.tcPr), tcPrPt);
+ var newTcPrPt = new XElement(tcPrPt);
+ newTcPrPt.Name = PtOpenXml.pt + "tcPr";
+ if (tcPrPtExists)
+ cell.Element(PtOpenXml.pt + "tcPr").ReplaceWith(newTcPrPt);
+ else
+ cell.Add(newTcPrPt);
+ }
+ }
+ }
+ }
+ }
+ else if (attName == W.firstColumn ||
+ attName == W.lastColumn ||
+ attName == W.oddVBand ||
+ attName == W.evenVBand)
+ {
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ ApplyCndFmtToCell(style, ot, attName, cell);
+ }
+ }
+ }
+ else if (attName == W.firstRowLastColumn)
+ {
+ var row = tbl.Elements(W.tr).FirstOrDefault();
+ if (row != null)
+ {
+ var cell = row.Elements(W.tc).LastOrDefault();
+ if (cell != null)
+ ApplyCndFmtToCell(style, ot, attName, cell);
+ }
+ }
+ else if (attName == W.firstRowFirstColumn)
+ {
+ var row = tbl.Elements(W.tr).FirstOrDefault();
+ if (row != null)
+ {
+ var cell = row.Elements(W.tc).FirstOrDefault();
+ if (cell != null)
+ ApplyCndFmtToCell(style, ot, attName, cell);
+ }
+ }
+ else if (attName == W.lastRowLastColumn)
+ {
+ var row = tbl.Elements(W.tr).LastOrDefault();
+ if (row != null)
+ {
+ var cell = row.Elements(W.tc).LastOrDefault();
+ if (cell != null)
+ ApplyCndFmtToCell(style, ot, attName, cell);
+ }
+ }
+ else if (attName == W.lastRowFirstColumn)
+ {
+ var row = tbl.Elements(W.tr).LastOrDefault();
+ if (row != null)
+ {
+ var cell = row.Elements(W.tc).FirstOrDefault();
+ if (cell != null)
+ ApplyCndFmtToCell(style, ot, attName, cell);
+ }
+ }
+ }
+ ProcessInnerBorders(tbl, style);
+ }
+ else
+ {
+ var tblPr = new XElement(W.tblPr);
+ XElement tblPr3 = MergeStyleElement(tbl.Element(W.tblPr), tblPr, true);
+ if (tblPr3 != null)
+ {
+ XElement newTblPr = new XElement(tblPr3);
+ newTblPr.Name = PtOpenXml.pt + "tblPr";
+ tbl.Add(newTblPr);
+ }
+
+ AddTcPrPtToEveryCell(tbl);
+ }
+ RollInDirectFormatting(tbl); // it is important that this is last. This merges in direct formatting.
+ }
+ }
+
+ private static void AddTcPrPtToEveryCell(XElement tbl)
+ {
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr");
+ if (tcPrPt != null)
+ continue;
+ tcPrPt = new XElement(PtOpenXml.pt + "tcPr",
+ new XElement(W.tcBorders));
+ cell.Add(tcPrPt);
+ }
+ }
+ }
+
+ private static void ApplyCndFmtToCell(XElement style, string ot, XName attName, XElement cell)
+ {
+ XElement cellCnf = cell.Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault();
+ if (cellCnf != null && cellCnf.Attribute(attName).ToBoolean() == true)
+ {
+ XElement o = style
+ .Elements(W.tblStylePr)
+ .Where(tsp => (string)tsp.Attribute(W.type) == ot)
+ .FirstOrDefault();
+ if (o != null)
+ {
+ bool tcPrPtExists = false;
+ var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr");
+ if (tcPrPt != null)
+ tcPrPtExists = true;
+ else
+ tcPrPt = new XElement(W.tcPr);
+ tcPrPt = MergeStyleElement(o.Element(W.tcPr), tcPrPt);
+ var newTcPrPt = new XElement(tcPrPt);
+ newTcPrPt.Name = PtOpenXml.pt + "tcPr";
+ if (tcPrPtExists)
+ cell.Element(PtOpenXml.pt + "tcPr").ReplaceWith(newTcPrPt);
+ else
+ cell.Add(newTcPrPt);
+ }
+ }
+ }
+
+ private static void RollInDirectFormatting(XElement tbl)
+ {
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var ptTcPr = cell.Element(PtOpenXml.pt + "tcPr");
+ var tcPr = cell.Element(W.tcPr);
+ var mTcPr = MergeStyleElement(tcPr, ptTcPr);
+ if (mTcPr == null)
+ {
+ mTcPr = new XElement(PtOpenXml.pt + "tcPr");
+ cell.Add(tcPr);
+ }
+ var newTcPr = new XElement(mTcPr);
+ newTcPr.Name = PtOpenXml.pt + "tcPr";
+ var existing = cell.Element(PtOpenXml.pt + "tcPr");
+ if (existing != null)
+ existing.ReplaceWith(newTcPr);
+ else
+ cell.Add(newTcPr);
+ }
+ }
+ var tblBorders = tbl.Elements(PtOpenXml.pt + "tblPr").Elements(W.tblBorders).FirstOrDefault();
+ if (tblBorders != null && tblBorders.Attribute(PtOpenXml.pt + "fromDirect") != null)
+ {
+ ApplyTblBordersToTable(tbl, tblBorders);
+ ProcessInnerBordersPerTblBorders(tbl, tblBorders);
+ }
+ }
+
+ private static void ApplyTblBordersToTable(XElement tbl, XElement tblBorders)
+ {
+ var top = tblBorders.Element(W.top);
+ if (top != null)
+ {
+ var firstRow = tbl.Elements(W.tr).FirstOrDefault();
+ if (firstRow != null)
+ {
+ foreach (var cell in firstRow.Elements(W.tc))
+ {
+ var cellTcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ if (cellTcBorders != null)
+ {
+ var cellTop = cellTcBorders.Element(W.top);
+ if (cellTop == null)
+ cellTcBorders.Add(top);
+ else
+ cellTop.ReplaceAttributes(top.Attributes());
+ }
+ }
+ }
+ }
+ var bottom = tblBorders.Element(W.bottom);
+ if (bottom != null)
+ {
+ var lastRow = tbl.Elements(W.tr).LastOrDefault();
+ if (lastRow != null)
+ {
+ foreach (var cell in lastRow.Elements(W.tc))
+ {
+ var cellTcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ if (cellTcBorders != null)
+ {
+ var cellBottom = cellTcBorders.Element(W.bottom);
+ if (cellBottom == null)
+ cellTcBorders.Add(bottom);
+ else
+ cellBottom.ReplaceAttributes(bottom.Attributes());
+ }
+ }
+ }
+ }
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ var left = tblBorders.Element(W.left);
+ if (left != null)
+ {
+ var firstCell = row.Elements(W.tc).FirstOrDefault();
+ if (firstCell != null)
+ {
+ var cellTcBorders = firstCell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ if (cellTcBorders != null)
+ {
+ var firstCellLeft = cellTcBorders.Element(W.left);
+ if (firstCellLeft == null)
+ cellTcBorders.Add(left);
+ else
+ firstCellLeft.ReplaceAttributes(left.Attributes());
+ }
+ }
+ }
+ var right = tblBorders.Element(W.right);
+ if (right != null)
+ {
+ var lastCell = row.Elements(W.tc).LastOrDefault();
+ if (lastCell != null)
+ {
+ var cellTcBorders = lastCell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ if (cellTcBorders != null)
+ {
+ var lastCellRight = cellTcBorders.Element(W.right);
+ if (lastCellRight == null)
+ cellTcBorders.Add(right);
+ else
+ lastCellRight.ReplaceAttributes(right.Attributes());
+ }
+ }
+ }
+ }
+ }
+
+ private static void AddOuterBorders(XElement tbl, XElement style)
+ {
+ var tblBorders = tbl.Elements(PtOpenXml.pt + "tblPr").Elements(W.tblBorders).FirstOrDefault();
+ if (tblBorders != null)
+ ApplyTblBordersToTable(tbl, tblBorders);
+ }
+
+ private static void ProcessInnerBorders(XElement tbl, XElement style)
+ {
+ var tblBorders = tbl.Elements(PtOpenXml.pt + "tblPr").Elements(W.tblBorders).FirstOrDefault();
+ if (tblBorders != null)
+ ProcessInnerBordersPerTblBorders(tbl, tblBorders);
+
+ foreach (var attName in new[] { W.oddHBand, W.evenHBand, W.firstRow, W.lastRow })
+ {
+ int rowCount = tbl.Elements(W.tr).Count();
+ int lastRow = rowCount - 1;
+ XElement insideV = null;
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ var rowCnfStyle = row.Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault();
+ if (rowCnfStyle != null)
+ {
+ var shouldApply = rowCnfStyle.Attribute(attName).ToBoolean();
+ if (shouldApply == true)
+ {
+ var cndType = TableStyleOverrideXNameRevMap[attName];
+ var cndStyle = style
+ .Elements(W.tblStylePr)
+ .FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == cndType);
+ if (cndStyle != null)
+ {
+ var styleTcBorders = cndStyle.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault();
+ if (styleTcBorders != null)
+ {
+ var top = styleTcBorders.Element(W.top);
+ var left = styleTcBorders.Element(W.left);
+ var bottom = styleTcBorders.Element(W.bottom);
+ var right = styleTcBorders.Element(W.right);
+ insideV = cndStyle.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.insideV).FirstOrDefault();
+ if (insideV != null)
+ {
+ int lastCol = row.Elements(W.tc).Count() - 1;
+ int colIdx = 0;
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ if (colIdx == 0)
+ {
+ ResolveInsideWithExisting(tcBorders, insideV, W.right);
+ }
+ else if (colIdx == lastCol)
+ {
+ ResolveInsideWithExisting(tcBorders, insideV, W.left);
+ }
+ else
+ {
+ ResolveInsideWithExisting(tcBorders, insideV, W.left);
+ ResolveInsideWithExisting(tcBorders, insideV, W.right);
+ }
+ colIdx++;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ foreach (var attName in new[] { W.oddVBand, W.evenVBand, W.firstColumn, W.lastColumn })
+ {
+ int rowIdx = 0;
+ int lastRow = tbl.Elements(W.tr).Count() - 1;
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var cellCnfStyle = cell.Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault();
+ if (cellCnfStyle != null)
+ {
+ var shouldApply = cellCnfStyle.Attribute(attName).ToBoolean();
+ if (shouldApply == true)
+ {
+ var cndType = TableStyleOverrideXNameRevMap[attName];
+ var cndStyle = style
+ .Elements(W.tblStylePr)
+ .FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == cndType);
+ if (cndStyle != null)
+ {
+ var insideH = cndStyle.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.insideH).FirstOrDefault();
+ if (insideH != null)
+ {
+ var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ if (rowIdx == 0)
+ {
+ ResolveInsideWithExisting(tcBorders, insideH, W.bottom);
+ }
+ else if (rowIdx == lastRow)
+ {
+ ResolveInsideWithExisting(tcBorders, insideH, W.top);
+ }
+ else
+ {
+ ResolveInsideWithExisting(tcBorders, insideH, W.bottom);
+ ResolveInsideWithExisting(tcBorders, insideH, W.top);
+ }
+ }
+ }
+ }
+ }
+ }
+ rowIdx++;
+ }
+ }
+ }
+
+ private static void ProcessInnerBordersPerTblBorders(XElement tbl, XElement tblBorders)
+ {
+ var tblInsideV = tblBorders.Elements(W.insideV).FirstOrDefault();
+ if (tblInsideV != null)
+ {
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ var lastCell = row.Elements(W.tc).Count() - 1;
+ int cellIdx = 0;
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var tcPr = cell.Element(PtOpenXml.pt + "tcPr");
+ if (tcPr == null)
+ {
+ tcPr = new XElement(PtOpenXml.pt + "tcPr");
+ cell.Add(tcPr);
+ }
+ var tcBorders = tcPr.Element(W.tcBorders);
+ if (tcBorders == null)
+ {
+ tcBorders = new XElement(W.tcBorders);
+ tcPr.Add(tcBorders);
+ }
+ if (cellIdx == 0)
+ {
+ ResolveInsideWithExisting(tcBorders, tblInsideV, W.right);
+ }
+ else if (cellIdx == lastCell)
+ {
+ ResolveInsideWithExisting(tcBorders, tblInsideV, W.left);
+ }
+ else
+ {
+ ResolveInsideWithExisting(tcBorders, tblInsideV, W.left);
+ ResolveInsideWithExisting(tcBorders, tblInsideV, W.right);
+ }
+ cellIdx++;
+ }
+ }
+ }
+ var tblInsideH = tblBorders.Elements(W.insideH).FirstOrDefault();
+ if (tblInsideH != null)
+ {
+ int rowIdx1 = 0;
+ int lastRow1 = tbl.Elements(W.tr).Count() - 1;
+ foreach (var row in tbl.Elements(W.tr))
+ {
+ if (rowIdx1 == 0)
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ ResolveInsideWithExisting(tcBorders, tblInsideH, W.bottom);
+ }
+ }
+ else if (rowIdx1 == lastRow1)
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ ResolveInsideWithExisting(tcBorders, tblInsideH, W.top);
+ }
+ }
+ else
+ {
+ foreach (var cell in row.Elements(W.tc))
+ {
+ var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault();
+ ResolveInsideWithExisting(tcBorders, tblInsideH, W.top);
+ ResolveInsideWithExisting(tcBorders, tblInsideH, W.bottom);
+ }
+ }
+ rowIdx1++;
+ }
+ }
+ }
+
+ private static void ResolveInsideWithExisting(XElement tcBorders, XElement inside, XName whichSide)
+ {
+ if (tcBorders.Element(whichSide) != null)
+ {
+ var newInsideH = ResolveInsideBorder(inside, tcBorders.Element(whichSide));
+ tcBorders.Element(whichSide).ReplaceAttributes(
+ newInsideH.Attributes());
+ }
+ else
+ {
+ tcBorders.Add(
+ new XElement(whichSide,
+ inside.Attributes()));
+ }
+ }
+
+ private static Dictionary<string, int> BorderTypePriority = new Dictionary<string, int>()
+ {
+ { "single", 1 },
+ { "thick", 2 },
+ { "double", 3 },
+ { "dotted", 4 },
+ };
+
+ private static Dictionary<string, int> BorderNumber = new Dictionary<string, int>()
+ {
+ {"single", 1 },
+ {"thick", 2 },
+ {"double", 3 },
+ {"dotted", 4 },
+ {"dashed", 5 },
+ {"dotDash", 6 },
+ {"dotDotDash", 7 },
+ {"triple", 8 },
+ {"thinThickSmallGap", 9 },
+ {"thickThinSmallGap", 10 },
+ {"thinThickThinSmallGap", 11 },
+ {"thinThickMediumGap", 12 },
+ {"thickThinMediumGap", 13 },
+ {"thinThickThinMediumGap", 14 },
+ {"thinThickLargeGap", 15 },
+ {"thickThinLargeGap", 16 },
+ {"thinThickThinLargeGap", 17 },
+ {"wave", 18 },
+ {"doubleWave", 19 },
+ {"dashSmallGap", 20 },
+ {"dashDotStroked", 21 },
+ {"threeDEmboss", 22 },
+ {"threeDEngrave", 23 },
+ {"outset", 24 },
+ {"inset", 25 },
+ };
+
+ private static XElement ResolveInsideBorder(XElement inside1, XElement sideToReplace)
+ {
+ if (inside1 == null && sideToReplace == null)
+ return null;
+ if (inside1 == null)
+ return sideToReplace;
+ if (sideToReplace == null)
+ return inside1;
+
+ // The following handles the situation where
+ // if table innerV is set, and cnd format for first row specifies nill border, then nil border wins.
+ // if table innerH is set, and cnd format for first columns specifies nil border, then table innerH wins.
+ if (sideToReplace.Name == W.left ||
+ sideToReplace.Name == W.right)
+ {
+ if ((string)inside1.Attribute(W.val) == "nil")
+ return inside1;
+ if ((string)sideToReplace.Attribute(W.val) == "nil")
+ return sideToReplace;
+ }
+ else
+ {
+ if ((string)inside1.Attribute(W.val) == "nil")
+ return sideToReplace;
+ if ((string)sideToReplace.Attribute(W.val) == "nil")
+ return inside1;
+ }
+
+ var inside1Val = (string)inside1.Attribute(W.val);
+ var border1Weight = 1;
+ if (BorderNumber.ContainsKey(inside1Val))
+ border1Weight = BorderNumber[inside1Val];
+
+ var sideToReplaceVal = (string)sideToReplace.Attribute(W.val);
+ var sideToReplaceWeight = 1;
+ if (BorderNumber.ContainsKey(sideToReplaceVal))
+ sideToReplaceWeight = BorderNumber[sideToReplaceVal];
+
+ if (border1Weight != sideToReplaceWeight)
+ {
+ if (border1Weight < sideToReplaceWeight)
+ return sideToReplace;
+ else
+ return inside1;
+ }
+
+ if ((int)inside1.Attribute(W.sz) > (int)sideToReplace.Attribute(W.sz))
+ return inside1;
+
+ if ((int)sideToReplace.Attribute(W.sz) > (int)inside1.Attribute(W.sz))
+ return sideToReplace;
+
+ if (BorderTypePriority.ContainsKey(inside1Val) &&
+ BorderTypePriority.ContainsKey(sideToReplaceVal))
+ {
+ var inside1Pri = BorderTypePriority[inside1Val];
+ var inside2Pri = BorderTypePriority[sideToReplaceVal];
+ if (inside1Pri > inside2Pri)
+ return inside1;
+ if (inside2Pri > inside1Pri)
+ return sideToReplace;
+ }
+
+ if ((int)inside1.Attribute(W.sz) > (int)sideToReplace.Attribute(W.sz))
+ return inside1;
+
+ if ((int)sideToReplace.Attribute(W.sz) > (int)inside1.Attribute(W.sz))
+ return sideToReplace;
+
+ var color1str = (string)inside1.Attribute(W.color);
+ if (color1str == "auto")
+ color1str = "000000";
+ var color2str = (string)sideToReplace.Attribute(W.color);
+ if (color2str == "auto")
+ color2str = "000000";
+ if (color1str != null && color2str != null && color1str != color2str)
+ {
+ Int32 color1;
+ Int32 color2;
+ try
+ {
+ color1 = Convert.ToInt32(color1str, 16);
+ }
+ // if the above throws ArgumentException, FormatException, or OverflowException, then abort
+ catch (Exception)
+ {
+ return sideToReplace;
+ }
+ try
+ {
+ color2 = Convert.ToInt32(color2str, 16);
+ }
+ // if the above throws ArgumentException, FormatException, or OverflowException, then abort
+ catch (Exception)
+ {
+ return inside1;
+ }
+ if (color1 < color2)
+ return inside1;
+ if (color2 < color1)
+ return sideToReplace;
+ return inside1;
+ }
+ return inside1;
+ }
+
+ private static XElement TableStyleRollup(WordprocessingDocument wDoc, string tblStyleName)
+ {
+ var tblStyleChain = TableStyleStack(wDoc, tblStyleName)
+ .Reverse();
+ XElement rolledStyle = new XElement(W.style);
+ foreach (var style in tblStyleChain)
+ {
+ rolledStyle = MergeStyleElement(style, rolledStyle);
+ }
+ return rolledStyle;
+ }
+
+ private static XName[] SpecialCaseChildProperties =
+ {
+ W.tblPr,
+ W.trPr,
+ W.tcPr,
+ W.pPr,
+ W.rPr,
+ W.pBdr,
+ W.tabs,
+ W.rFonts,
+ W.ind,
+ W.spacing,
+ W.tblStylePr,
+ W.tcBorders,
+ W.tblBorders,
+ W.lang,
+ W.numPr,
+ };
+
+ private static XName[] MergeChildProperties =
+ {
+ W.tblPr,
+ W.trPr,
+ W.tcPr,
+ W.pPr,
+ W.rPr,
+ W.pBdr,
+ W.tcBorders,
+ W.tblBorders,
+ W.numPr,
+ };
+
+ private static string[] TableStyleOverrideTypes =
+ {
+ "band1Vert",
+ "band2Vert",
+ "band1Horz",
+ "band2Horz",
+ "firstCol",
+ "lastCol",
+ "firstRow",
+ "lastRow",
+ "neCell",
+ "nwCell",
+ "seCell",
+ "swCell",
+ };
+
+ private static Dictionary<string, XName> TableStyleOverrideXNameMap = new Dictionary<string, XName>
+ {
+ {"band1Vert", W.oddVBand},
+ {"band2Vert", W.evenVBand},
+ {"band1Horz", W.oddHBand},
+ {"band2Horz", W.evenHBand},
+ {"firstCol", W.firstColumn},
+ {"lastCol", W.lastColumn},
+ {"firstRow", W.firstRow},
+ {"lastRow", W.lastRow},
+ {"neCell", W.firstRowLastColumn},
+ {"nwCell", W.firstRowFirstColumn},
+ {"seCell", W.lastRowLastColumn},
+ {"swCell", W.lastRowFirstColumn},
+ };
+
+ private static Dictionary<XName, string> TableStyleOverrideXNameRevMap = new Dictionary<XName, string>
+ {
+ {W.oddVBand, "band1Vert"},
+ {W.evenVBand, "band2Vert"},
+ {W.oddHBand, "band1Horz"},
+ {W.evenHBand, "band2Horz"},
+ {W.firstColumn, "firstCol"},
+ {W.lastColumn, "lastCol"},
+ {W.firstRow, "firstRow"},
+ {W.lastRow, "lastRow"},
+ };
+
+ private static XElement MergeStyleElement(XElement higherPriorityElement, XElement lowerPriorityElement)
+ {
+ return MergeStyleElement(higherPriorityElement, lowerPriorityElement, null);
+ }
+
+ private static XElement MergeStyleElement(XElement higherPriorityElement, XElement lowerPriorityElement, bool? highPriIsDirectFormatting)
+ {
+ // If, when in the process of merging, the source element doesn't have a
+ // corresponding element in the merged element, then include the source element
+ // in the merged element.
+ if (lowerPriorityElement == null)
+ return higherPriorityElement;
+ if (higherPriorityElement == null)
+ return lowerPriorityElement;
+
+ var hpe = higherPriorityElement
+ .Elements()
+ .Where(e => !SpecialCaseChildProperties.Contains(e.Name))
+ .ToArray();
+ if (highPriIsDirectFormatting == true)
+ {
+ hpe = hpe
+ .Select(e =>
+ new XElement(e.Name,
+ e.Attributes(),
+ new XAttribute(PtOpenXml.pt + "fromDirect", true),
+ e.Elements()))
+ .ToArray();
+ }
+ var lpe = lowerPriorityElement
+ .Elements()
+ .Where(e => !SpecialCaseChildProperties.Contains(e.Name) && !hpe.Select(z => z.Name).Contains(e.Name))
+ .ToArray();
+ var ma = SpacingMerge(higherPriorityElement.Element(W.spacing), lowerPriorityElement.Element(W.spacing));
+ var rFonts = FontMerge(higherPriorityElement.Element(W.rFonts), lowerPriorityElement.Element(W.rFonts));
+ var tabs = TabsMerge(higherPriorityElement.Element(W.tabs), lowerPriorityElement.Element(W.tabs));
+ var ind = IndMerge(higherPriorityElement.Element(W.ind), lowerPriorityElement.Element(W.ind));
+ var lang = LangMerge(higherPriorityElement.Element(W.lang), lowerPriorityElement.Element(W.lang));
+ var mcp = MergeChildProperties
+ .Select(e =>
+ {
+ // test is here to prevent unnecessary recursion to make debugging easier
+ var h = higherPriorityElement.Element(e);
+ var l = lowerPriorityElement.Element(e);
+ if (h == null && l == null)
+ return null;
+ if (h == null && l != null)
+ return l;
+ if (h != null && l == null)
+ {
+ var newH = new XElement(h.Name,
+ h.Attributes(),
+ highPriIsDirectFormatting == true ? new XAttribute(PtOpenXml.pt + "fromDirect", true) : null,
+ h.Elements());
+ return newH;
+ }
+ return MergeStyleElement(h, l, highPriIsDirectFormatting);
+ })
+ .Where(m => m != null)
+ .ToArray();
+ var tsor = TableStyleOverrideTypes
+ .Select(e =>
+ {
+ // test is here to prevent unnecessary recursion to make debugging easier
+ var h = higherPriorityElement.Elements(W.tblStylePr).FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == e);
+ var l = lowerPriorityElement.Elements(W.tblStylePr).FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == e);
+ if (h == null && l == null)
+ return null;
+ if (h == null && l != null)
+ return l;
+ if (h != null && l == null)
+ return h;
+ return MergeStyleElement(h, l);
+ })
+ .Where(m => m != null)
+ .ToArray();
+
+ XElement newMergedElement = new XElement(higherPriorityElement.Name,
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ higherPriorityElement.Attributes().Where(a => !a.IsNamespaceDeclaration),
+ hpe, // higher priority elements
+ lpe, // lower priority elements where there is not a higher priority element of same name
+ ind, // w:ind has very special rules
+ ma, // elements that require merged attributes
+ lang,
+ rFonts, // font merge is special case
+ tabs, // tabs merge is special case
+ mcp, // elements that need child properties to be merged
+ tsor // merged table style override elements
+ );
+
+ return newMergedElement;
+ }
+
+ private static XElement LangMerge(XElement hLang, XElement lLang)
+ {
+ if (hLang == null && lLang == null)
+ return null;
+ if (hLang != null && lLang == null)
+ return hLang;
+ if (lLang != null && hLang == null)
+ return lLang;
+ return new XElement(W.lang,
+ hLang.Attribute(W.val) != null ? hLang.Attribute(W.val) : lLang.Attribute(W.val),
+ hLang.Attribute(W.bidi) != null ? hLang.Attribute(W.bidi) : lLang.Attribute(W.bidi),
+ hLang.Attribute(W.eastAsia) != null ? hLang.Attribute(W.eastAsia) : lLang.Attribute(W.eastAsia));
+ }
+
+ private enum IndAttType
+ {
+ End,
+ FirstLineOrHanging,
+ Start,
+ Left,
+ Right,
+ None,
+ };
+
+ private static XElement IndMerge(XElement higherPriorityElement, XElement lowerPriorityElement)
+ {
+ if (higherPriorityElement == null && lowerPriorityElement == null)
+ return null;
+ if (higherPriorityElement != null && lowerPriorityElement == null)
+ return higherPriorityElement;
+ if (lowerPriorityElement != null && higherPriorityElement == null)
+ return lowerPriorityElement;
+
+ XElement hpe = new XElement(higherPriorityElement);
+ XElement lpe = new XElement(lowerPriorityElement);
+
+ if (hpe.Attribute(W.firstLine) != null)
+ lpe.Attributes(W.hanging).Remove();
+
+ if (hpe.Attribute(W.firstLineChars) != null)
+ lpe.Attributes(W.hangingChars).Remove();
+
+ if (hpe.Attribute(W.hanging) != null)
+ lpe.Attributes(W.firstLine).Remove();
+
+ if (hpe.Attribute(W.hangingChars) != null)
+ lpe.Attributes(W.firstLineChars).Remove();
+
+ var highPriAtts = hpe
+ .Attributes()
+ .Where(a => !a.IsNamespaceDeclaration)
+ .ToList();
+
+ var highPriAttNames = highPriAtts
+ .Select(a => a.Name);
+
+ var lowPriAtts = lpe
+ .Attributes()
+ .Where(a => !a.IsNamespaceDeclaration)
+ .Where(a => !highPriAttNames.Contains(a.Name))
+ .ToList();
+
+ var mergedElement = new XElement(higherPriorityElement.Name,
+ highPriAtts,
+ lowPriAtts);
+
+ return mergedElement;
+ }
+
+ // merge child tab elements
+ // they are additive, with the exception that if there are two elements at the same location,
+ // we need to take the higher, and not take the lower.
+ private static XElement TabsMerge(XElement higherPriorityElement, XElement lowerPriorityElement)
+ {
+ if (higherPriorityElement != null && lowerPriorityElement == null)
+ return higherPriorityElement;
+ if (higherPriorityElement == null && lowerPriorityElement != null)
+ return lowerPriorityElement;
+ if (higherPriorityElement == null && lowerPriorityElement == null)
+ return null;
+ var hps = higherPriorityElement.Elements().Select(e =>
+ new
+ {
+ Pos = (int)e.Attribute(W.pos),
+ Pri = 1,
+ Element = e,
+ }
+ );
+ var lps = lowerPriorityElement.Elements().Select(e =>
+ new
+ {
+ Pos = (int)e.Attribute(W.pos),
+ Pri = 2,
+ Element = e,
+ }
+ );
+ var newTabElements = hps.Concat(lps)
+ .GroupBy(s => s.Pos)
+ .Select(g => g.OrderBy(s => s.Pri).First().Element)
+ .Where(e => (string)e.Attribute(W.val) != "clear")
+ .OrderBy(e => (int)e.Attribute(W.pos));
+ var newTabs = new XElement(W.tabs, newTabElements);
+ return newTabs;
+ }
+
+ private static XElement SpacingMerge(XElement hn, XElement ln)
+ {
+ if (hn == null && ln == null)
+ return null;
+ if (hn != null && ln == null)
+ return hn;
+ if (hn == null && ln != null)
+ return ln;
+ var mn1 = new XElement(W.spacing,
+ hn.Attributes(),
+ ln.Attributes().Where(a => hn.Attribute(a.Name) == null));
+ return mn1;
+ }
+
+ private static IEnumerable<XElement> TableStyleStack(WordprocessingDocument wDoc, string tblStyleName)
+ {
+ XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ string currentStyle = tblStyleName;
+ while (true)
+ {
+ XElement style = sXDoc
+ .Root
+ .Elements(W.style).Where(s => (string)s.Attribute(W.type) == "table" &&
+ (string)s.Attribute(W.styleId) == currentStyle)
+ .FirstOrDefault();
+ if (style == null)
+ yield break;
+ yield return style;
+ currentStyle = (string)style.Elements(W.basedOn).Attributes(W.val).FirstOrDefault();
+ if (currentStyle == null)
+ yield break;
+ }
+ }
+
+ private static XElement FontMerge(XElement higherPriorityFont, XElement lowerPriorityFont)
+ {
+ XElement rFonts;
+
+ if (higherPriorityFont == null)
+ return lowerPriorityFont;
+ if (lowerPriorityFont == null)
+ return higherPriorityFont;
+ if (higherPriorityFont == null && lowerPriorityFont == null)
+ return null;
+
+ rFonts = new XElement(W.rFonts,
+ (higherPriorityFont.Attribute(W.ascii) != null || higherPriorityFont.Attribute(W.asciiTheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.ascii), higherPriorityFont.Attribute(W.asciiTheme) } :
+ new[] { lowerPriorityFont.Attribute(W.ascii), lowerPriorityFont.Attribute(W.asciiTheme) },
+ (higherPriorityFont.Attribute(W.hAnsi) != null || higherPriorityFont.Attribute(W.hAnsiTheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.hAnsi), higherPriorityFont.Attribute(W.hAnsiTheme) } :
+ new[] { lowerPriorityFont.Attribute(W.hAnsi), lowerPriorityFont.Attribute(W.hAnsiTheme) },
+ (higherPriorityFont.Attribute(W.eastAsia) != null || higherPriorityFont.Attribute(W.eastAsiaTheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.eastAsia), higherPriorityFont.Attribute(W.eastAsiaTheme) } :
+ new[] { lowerPriorityFont.Attribute(W.eastAsia), lowerPriorityFont.Attribute(W.eastAsiaTheme) },
+ (higherPriorityFont.Attribute(W.cs) != null || higherPriorityFont.Attribute(W.cstheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.cs), higherPriorityFont.Attribute(W.cstheme) } :
+ new[] { lowerPriorityFont.Attribute(W.cs), lowerPriorityFont.Attribute(W.cstheme) },
+ (higherPriorityFont.Attribute(W.hint) != null ? higherPriorityFont.Attribute(W.hint) :
+ lowerPriorityFont.Attribute(W.hint))
+ );
+
+ return rFonts;
+ }
+
+ private static void AnnotateParagraphs(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement root, FormattingAssemblerSettings settings)
+ {
+ foreach (var para in root.Descendants(W.p))
+ {
+ AnnotateParagraph(fai, wDoc, para, settings);
+ }
+ }
+
+ private static void AnnotateParagraph(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement para, FormattingAssemblerSettings settings)
+ {
+ XElement localParaProps = para.Element(W.pPr);
+ if (localParaProps == null)
+ {
+ localParaProps = new XElement(W.pPr);
+ }
+
+ // get para table props, to be merged.
+ XElement tablepPr = null;
+
+ var blockLevelContentContainer = para
+ .Ancestors()
+ .FirstOrDefault(a => a.Name == W.body ||
+ a.Name == W.tbl ||
+ a.Name == W.txbxContent ||
+ a.Name == W.ftr ||
+ a.Name == W.hdr ||
+ a.Name == W.footnote ||
+ a.Name == W.endnote);
+ if (blockLevelContentContainer.Name == W.tbl)
+ {
+ XElement tbl = blockLevelContentContainer;
+ XElement style = tbl.Element(PtOpenXml.pt + "style");
+ XElement cellCnf = para.Ancestors(W.tc).Take(1).Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault();
+ XElement rowCnf = para.Ancestors(W.tr).Take(1).Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault();
+
+ if (style != null)
+ {
+ // roll up tblPr, trPr, and tcPr from within a specific style.
+ // add each of these to the table, in PowerTools namespace.
+ tablepPr = style.Element(W.pPr);
+ if (tablepPr == null)
+ tablepPr = new XElement(W.pPr);
+
+ foreach (var ot in TableStyleOverrideTypes)
+ {
+ XName attName = TableStyleOverrideXNameMap[ot];
+ if ((cellCnf != null && cellCnf.Attribute(attName).ToBoolean() == true) ||
+ (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true))
+ {
+ XElement o = style
+ .Elements(W.tblStylePr)
+ .Where(tsp => (string)tsp.Attribute(W.type) == ot)
+ .FirstOrDefault();
+ if (o != null)
+ {
+ XElement otpPr = o.Element(W.pPr);
+ tablepPr = MergeStyleElement(otpPr, tablepPr);
+ }
+ }
+ }
+ }
+ }
+ var stylesPart = wDoc.MainDocumentPart.StyleDefinitionsPart;
+ XDocument sXDoc = null;
+ if (stylesPart != null)
+ sXDoc = stylesPart.GetXDocument();
+
+ ListItemRetriever.ListItemInfo lif = para.Annotation<ListItemRetriever.ListItemInfo>();
+
+ XElement rolledParaProps = ParagraphStyleRollup(para, sXDoc, fai.DefaultParagraphStyleName);
+ if (lif != null && lif.IsZeroNumId)
+ rolledParaProps.Elements(W.ind).Remove();
+ XElement toggledParaProps = MergeStyleElement(rolledParaProps, tablepPr);
+ XElement mergedParaProps = MergeStyleElement(localParaProps, toggledParaProps);
+
+ string li = ListItemRetriever.RetrieveListItem(wDoc, para, settings.ListItemRetrieverSettings);
+ if (lif != null && lif.IsListItem)
+ {
+ if (settings.RestrictToSupportedNumberingFormats)
+ {
+ string numFmtForLevel = (string)lif.Lvl(ListItemRetriever.GetParagraphLevel(para)).Elements(W.numFmt).Attributes(W.val).FirstOrDefault();
+ if (numFmtForLevel == null)
+ {
+ var numFmtElement = lif.Lvl(ListItemRetriever.GetParagraphLevel(para)).Elements(MC.AlternateContent).Elements(MC.Choice).Elements(W.numFmt).FirstOrDefault();
+ if (numFmtElement != null && (string)numFmtElement.Attribute(W.val) == "custom")
+ numFmtForLevel = (string)numFmtElement.Attribute(W.format);
+ }
+ bool isLgl = lif.Lvl(ListItemRetriever.GetParagraphLevel(para)).Elements(W.isLgl).Any();
+ if (isLgl && numFmtForLevel != "decimalZero")
+ numFmtForLevel = "decimal";
+ if (!AcceptableNumFormats.Contains(numFmtForLevel))
+ throw new UnsupportedNumberingFormatException(numFmtForLevel + " is not a supported numbering format");
+ }
+
+ int paragraphLevel = ListItemRetriever.GetParagraphLevel(para);
+ var numberingParaProps = lif
+ .Lvl(paragraphLevel)
+ .Elements(W.pPr)
+ .FirstOrDefault();
+ if (numberingParaProps == null)
+ {
+ numberingParaProps = new XElement(W.pPr);
+ }
+ else
+ {
+ numberingParaProps
+ .Elements()
+ .Where(e => e.Name != W.ind)
+ .Remove();
+ }
+
+ // have:
+ // - localParaProps
+ // - toggledParaProps
+ // - numberingParaProps
+
+ // if a paragraph contains a numPr with a numId=0, in other words, it is NOT a numbered item, then the indentation from the style
+ // hierarchy is ignored.
+
+ ListItemRetriever.ListItemInfo lii = para.Annotation<ListItemRetriever.ListItemInfo>();
+ if (lii.FromParagraph != null)
+ {
+ // order
+ // - toggledParaProps
+ // - numberingParaProps
+ // - localParaProps
+
+ mergedParaProps = MergeStyleElement(numberingParaProps, toggledParaProps);
+ mergedParaProps = MergeStyleElement(localParaProps, mergedParaProps);
+ }
+ else if (lii.FromStyle != null)
+ {
+ // order
+ // - numberingParaProps
+ // - toggledParaProps
+ // - localParaProps
+ mergedParaProps = MergeStyleElement(toggledParaProps, numberingParaProps);
+ mergedParaProps = MergeStyleElement(localParaProps, mergedParaProps);
+ }
+ }
+ else
+ {
+ mergedParaProps = MergeStyleElement(localParaProps, toggledParaProps);
+ }
+
+ // merge mergedParaProps with existing accumulatedParaProps, with mergedParaProps as high pri
+ // replace accumulatedParaProps with newly merged
+
+ XElement accumulatedParaProps = para.Element(PtOpenXml.pt + "pPr");
+ XElement newAccumulatedParaProps = MergeStyleElement(mergedParaProps, accumulatedParaProps);
+
+ AdjustFontAttributes(wDoc, para, newAccumulatedParaProps, newAccumulatedParaProps.Element(W.rPr), settings);
+ newAccumulatedParaProps.Name = PtOpenXml.pt + "pPr";
+ if (accumulatedParaProps != null)
+ {
+ accumulatedParaProps.ReplaceWith(newAccumulatedParaProps);
+ }
+ else
+ {
+ para.Add(newAccumulatedParaProps);
+ }
+ }
+
+ private static string[] AcceptableNumFormats = new[] {
+ "decimal",
+ "decimalZero",
+ "upperRoman",
+ "lowerRoman",
+ "upperLetter",
+ "lowerLetter",
+ "ordinal",
+ "cardinalText",
+ "ordinalText",
+ "bullet",
+ "0001, 0002, 0003, ...",
+ "none",
+ };
+
+ public static XElement ParagraphStyleRollup(XElement paragraph, XDocument stylesXDoc, string defaultParagraphStyleName)
+ {
+ var paraStyle = (string)paragraph
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ if (paraStyle == null)
+ paraStyle = defaultParagraphStyleName;
+ var rolledUpParaStyleParaProps = new XElement(W.pPr);
+ if (stylesXDoc == null)
+ return rolledUpParaStyleParaProps;
+ if (paraStyle != null)
+ {
+ rolledUpParaStyleParaProps = ParaStyleParaPropsStack(stylesXDoc, paraStyle, paragraph)
+ .Reverse()
+ .Aggregate(new XElement(W.pPr),
+ (r, s) =>
+ {
+ var newParaProps = MergeStyleElement(s, r);
+ return newParaProps;
+ });
+ }
+ return rolledUpParaStyleParaProps;
+ }
+
+ private static IEnumerable<XElement> ParaStyleParaPropsStack(XDocument stylesXDoc, string paraStyleName, XElement para)
+ {
+ if (stylesXDoc == null)
+ yield break;
+ var localParaStyleName = paraStyleName;
+ while (localParaStyleName != null)
+ {
+ XElement paraStyle = stylesXDoc.Root.Elements(W.style).FirstOrDefault(s =>
+ s.Attribute(W.type).Value == "paragraph" &&
+ s.Attribute(W.styleId).Value == localParaStyleName);
+ if (paraStyle == null)
+ {
+ yield break;
+ }
+ if (paraStyle.Element(W.pPr) == null)
+ {
+ if (paraStyle.Element(W.rPr) != null)
+ {
+ var elementToYield2 = new XElement(W.pPr,
+ paraStyle.Element(W.rPr));
+ yield return elementToYield2;
+ }
+ localParaStyleName = (string)(paraStyle
+ .Elements(W.basedOn)
+ .Attributes(W.val)
+ .FirstOrDefault());
+ continue;
+ }
+
+ var elementToYield = new XElement(W.pPr,
+ paraStyle.Element(W.pPr).Attributes(),
+ paraStyle.Element(W.pPr).Elements(),
+ paraStyle.Element(W.rPr));
+ yield return (elementToYield);
+
+ var listItemInfo = para.Annotation<ListItemRetriever.ListItemInfo>();
+ if (listItemInfo != null)
+ {
+ if (listItemInfo.IsListItem)
+ {
+ XElement lipPr = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(para)).Element(W.pPr);
+ if (lipPr == null)
+ lipPr = new XElement(W.pPr);
+ XElement lirPr = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(para)).Element(W.rPr);
+ var elementToYield2 = new XElement(W.pPr,
+ lipPr.Attributes(),
+ lipPr.Elements(),
+ lirPr);
+ yield return (elementToYield2);
+ }
+ }
+
+ localParaStyleName = (string)paraStyle
+ .Elements(W.basedOn)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ }
+ yield break;
+ }
+
+ private static void AnnotateRuns(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement root, FormattingAssemblerSettings settings)
+ {
+ var runsOrParas = root.Descendants()
+ .Where(rp =>
+ {
+ return rp.Name == W.r || rp.Name == W.p;
+ });
+ foreach (var runOrPara in runsOrParas)
+ {
+ AnnotateRunProperties(fai, wDoc, runOrPara, settings);
+ }
+ }
+
+ private static void AnnotateRunProperties(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement runOrPara, FormattingAssemblerSettings settings)
+ {
+ XElement localRunProps = null;
+ if (runOrPara.Name == W.p)
+ {
+ var rPr = runOrPara.Elements(W.pPr).Elements(W.rPr).FirstOrDefault();
+ if (rPr != null)
+ {
+ localRunProps = rPr;
+ }
+ }
+ else
+ {
+ localRunProps = runOrPara.Element(W.rPr);
+ }
+ if (localRunProps == null)
+ {
+ localRunProps = new XElement(W.rPr);
+ }
+
+ // get run table props, to be merged.
+ XElement tablerPr = null;
+ var blockLevelContentContainer = runOrPara
+ .Ancestors()
+ .FirstOrDefault(a => a.Name == W.body ||
+ a.Name == W.tbl ||
+ a.Name == W.txbxContent ||
+ a.Name == W.ftr ||
+ a.Name == W.hdr ||
+ a.Name == W.footnote ||
+ a.Name == W.endnote);
+ if (blockLevelContentContainer.Name == W.tbl)
+ {
+ XElement tbl = blockLevelContentContainer;
+ XElement style = tbl.Element(PtOpenXml.pt + "style");
+ XElement cellCnf = runOrPara.Ancestors(W.tc).Take(1).Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault();
+ XElement rowCnf = runOrPara.Ancestors(W.tr).Take(1).Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault();
+
+ if (style != null)
+ {
+ tablerPr = style.Element(W.rPr);
+ if (tablerPr == null)
+ tablerPr = new XElement(W.rPr);
+
+ foreach (var ot in TableStyleOverrideTypes)
+ {
+ XName attName = TableStyleOverrideXNameMap[ot];
+ if ((cellCnf != null && cellCnf.Attribute(attName).ToBoolean() == true) ||
+ (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true))
+ {
+ XElement o = style
+ .Elements(W.tblStylePr)
+ .Where(tsp => (string)tsp.Attribute(W.type) == ot)
+ .FirstOrDefault();
+ if (o != null)
+ {
+ XElement otrPr = o.Element(W.rPr);
+ tablerPr = MergeStyleElement(otrPr, tablerPr);
+ }
+ }
+ }
+ }
+ }
+ XElement rolledRunProps = CharStyleRollup(fai, wDoc, runOrPara);
+ var toggledRunProps = ToggleMergeRunProps(rolledRunProps, tablerPr);
+ var currentRunProps = runOrPara.Element(PtOpenXml.rPr); // this is already stored on the run from previous aggregation of props
+ var mergedRunProps = MergeStyleElement(toggledRunProps, currentRunProps);
+ var newMergedRunProps = MergeStyleElement(localRunProps, mergedRunProps);
+ XElement pPr = null;
+ if (runOrPara.Name == W.p)
+ pPr = runOrPara.Element(PtOpenXml.pPr);
+ AdjustFontAttributes(wDoc, runOrPara, pPr, newMergedRunProps, settings);
+
+ newMergedRunProps.Name = PtOpenXml.rPr;
+ if (currentRunProps != null)
+ {
+ currentRunProps.ReplaceWith(newMergedRunProps);
+ }
+ else
+ {
+ runOrPara.Add(newMergedRunProps);
+ }
+ }
+
+ private static XElement CharStyleRollup(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement runOrPara)
+ {
+ var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+
+ string charStyle = null;
+ string paraStyle = null;
+ XElement rPr = null;
+ XElement pPr = null;
+ XElement pStyle = null;
+ XElement rStyle = null;
+ CachedParaInfo cpi = null; // CachedParaInfo is an optimization for the case where a paragraph contains thousands of runs.
+
+ if (runOrPara.Name == W.p)
+ {
+ cpi = runOrPara.Annotation<CachedParaInfo>();
+ if (cpi != null)
+ pPr = cpi.ParagraphProperties;
+ else
+ {
+ pPr = runOrPara.Element(W.pPr);
+ if (pPr != null)
+ {
+ paraStyle = (string)pPr.Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
+ }
+ else
+ {
+ paraStyle = fai.DefaultParagraphStyleName;
+ }
+ cpi = new CachedParaInfo
+ {
+ ParagraphProperties = pPr,
+ ParagraphStyleName = paraStyle,
+ };
+ runOrPara.AddAnnotation(cpi);
+ }
+ if (pPr != null)
+ {
+ rPr = pPr.Element(W.rPr);
+ }
+ }
+ else
+ {
+ rPr = runOrPara.Element(W.rPr);
+ }
+ if (rPr != null)
+ {
+ rStyle = rPr.Element(W.rStyle);
+ if (rStyle != null)
+ {
+ charStyle = (string)rStyle.Attribute(W.val);
+ }
+ else
+ {
+ if (runOrPara.Name == W.r)
+ charStyle = (string)runOrPara
+ .Ancestors(W.p)
+ .Take(1)
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ else
+ charStyle = (string)runOrPara
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ }
+ }
+
+ if (charStyle == null)
+ {
+ if (runOrPara.Name == W.r)
+ {
+ var ancestorPara = runOrPara.Ancestors(W.p).First();
+ cpi = ancestorPara.Annotation<CachedParaInfo>();
+ if (cpi != null)
+ charStyle = cpi.ParagraphStyleName;
+ else
+ charStyle = (string)runOrPara.Ancestors(W.p).First().Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
+ }
+ if (charStyle == null)
+ {
+ charStyle = fai.DefaultParagraphStyleName;
+ }
+ }
+
+ // A run always must have an ancestor paragraph.
+ XElement para = null;
+ var rolledUpParaStyleRunProps = new XElement(W.rPr);
+ if (runOrPara.Name == W.r)
+ {
+ para = runOrPara.Ancestors(W.p).FirstOrDefault();
+ }
+ else
+ {
+ para = runOrPara;
+ }
+
+ cpi = para.Annotation<CachedParaInfo>();
+ if (cpi != null)
+ {
+ pPr = cpi.ParagraphProperties;
+ }
+ else
+ {
+ pPr = para.Element(W.pPr);
+ }
+ if (pPr != null)
+ {
+ pStyle = pPr.Element(W.pStyle);
+ if (pStyle != null)
+ {
+ paraStyle = (string)pStyle.Attribute(W.val);
+ }
+ else
+ {
+ paraStyle = fai.DefaultParagraphStyleName;
+ }
+ }
+ else
+ paraStyle = fai.DefaultParagraphStyleName;
+
+ string key = (paraStyle == null ? "[null]" : paraStyle) + "~|~" +
+ (charStyle == null ? "[null]" : charStyle);
+ XElement rolledRunProps = null;
+
+ if (fai.RolledCharacterStyles.ContainsKey(key))
+ rolledRunProps = fai.RolledCharacterStyles[key];
+ else
+ {
+ XElement rolledUpCharStyleRunProps = new XElement(W.rPr);
+ if (charStyle != null)
+ {
+ rolledUpCharStyleRunProps =
+ CharStyleStack(wDoc, charStyle)
+ .Aggregate(new XElement(W.rPr),
+ (r, s) =>
+ {
+ var newRunProps = MergeStyleElement(s, r);
+ return newRunProps;
+ });
+ }
+
+ if (paraStyle != null)
+ {
+ rolledUpParaStyleRunProps = ParaStyleRunPropsStack(wDoc, paraStyle)
+ .Aggregate(new XElement(W.rPr),
+ (r, s) =>
+ {
+ var newCharStyleRunProps = MergeStyleElement(s, r);
+ return newCharStyleRunProps;
+ });
+ }
+ rolledRunProps = MergeStyleElement(rolledUpCharStyleRunProps, rolledUpParaStyleRunProps);
+ fai.RolledCharacterStyles.Add(key, rolledRunProps);
+ }
+
+ return rolledRunProps;
+ }
+
+ private static IEnumerable<XElement> ParaStyleRunPropsStack(WordprocessingDocument wDoc, string paraStyleName)
+ {
+ var localParaStyleName = paraStyleName;
+ var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ var rValue = new Stack<XElement>();
+ while (localParaStyleName != null)
+ {
+ var paraStyle = sXDoc.Root.Elements(W.style).FirstOrDefault(s =>
+ {
+ return (string)s.Attribute(W.type) == "paragraph" &&
+ (string)s.Attribute(W.styleId) == localParaStyleName;
+ });
+ if (paraStyle == null)
+ {
+ return rValue;
+ }
+ if (paraStyle.Element(W.rPr) != null)
+ {
+ rValue.Push(paraStyle.Element(W.rPr));
+ }
+ localParaStyleName = (string)paraStyle
+ .Elements(W.basedOn)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ }
+ return rValue;
+ }
+
+ // returns collection of run properties
+ private static IEnumerable<XElement> CharStyleStack(WordprocessingDocument wDoc, string charStyleName)
+ {
+ var localCharStyleName = charStyleName;
+ var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ var rValue = new Stack<XElement>();
+ while (localCharStyleName != null)
+ {
+ XElement basedOn = null;
+ // first look for character style
+ var charStyle = sXDoc.Root.Elements(W.style).FirstOrDefault(s =>
+ {
+ return (string)s.Attribute(W.type) == "character" &&
+ (string)s.Attribute(W.styleId) == localCharStyleName;
+ });
+ // if not found, look for paragraph style
+ if (charStyle == null)
+ {
+ charStyle = sXDoc.Root.Elements(W.style).FirstOrDefault(s =>
+ {
+ return (string)s.Attribute(W.styleId) == localCharStyleName;
+ });
+ }
+ if (charStyle == null)
+ {
+ return rValue;
+ }
+ if (charStyle.Element(W.rPr) == null)
+ {
+ basedOn = charStyle.Element(W.basedOn);
+ if (basedOn != null)
+ {
+ localCharStyleName = (string)basedOn.Attribute(W.val);
+ }
+ else
+ {
+ return rValue;
+ }
+ }
+ rValue.Push(charStyle.Element(W.rPr));
+ localCharStyleName = null;
+ basedOn = charStyle.Element(W.basedOn);
+ if (basedOn != null)
+ {
+ localCharStyleName = (string)basedOn.Attribute(W.val);
+ }
+ }
+ return rValue;
+ }
+
+ private static XElement ToggleMergeRunProps(XElement higherPriorityElement, XElement lowerPriorityElement)
+ {
+ if (lowerPriorityElement == null)
+ return higherPriorityElement;
+ if (higherPriorityElement == null)
+ return lowerPriorityElement;
+
+ var hpe = higherPriorityElement.Elements().Select(e => e.Name).ToArray();
+
+ var newMergedElement = new XElement(higherPriorityElement.Name,
+ higherPriorityElement.Attributes(),
+
+ // process toggle properties
+ higherPriorityElement.Elements()
+ .Where(e => { return e.Name != W.rFonts; })
+ .Select(higherChildElement =>
+ {
+ if (TogglePropertyNames.Contains(higherChildElement.Name))
+ {
+ var lowerChildElement = lowerPriorityElement.Element(higherChildElement.Name);
+ if (lowerChildElement == null)
+ {
+ return higherChildElement;
+ }
+
+ var bHigher = higherChildElement.Attribute(W.val) == null || higherChildElement.Attribute(W.val).ToBoolean() == true;
+
+ var bLower = lowerChildElement.Attribute(W.val) == null || lowerChildElement.Attribute(W.val).ToBoolean() == true;
+
+ // if higher is true and lower is false, then return true element
+ if (bHigher && !bLower)
+ {
+ return higherChildElement;
+ }
+
+ // if higher is false and lower is true, then return false element
+ if (!bHigher && bLower)
+ {
+ return higherChildElement;
+ }
+
+ // if higher and lower are both true, then return false
+ if (bHigher && bLower)
+ {
+ return new XElement(higherChildElement.Name,
+ new XAttribute(W.val, "0"));
+ }
+
+ // otherwise, both higher and lower are false so can return higher element.
+ return higherChildElement;
+ }
+ return higherChildElement;
+ }),
+
+ FontMerge(higherPriorityElement.Element(W.rFonts), lowerPriorityElement.Element(W.rFonts)),
+
+ // take lower priority elements where there is not a higher priority element of same name
+ lowerPriorityElement.Elements()
+ .Where(e =>
+ {
+ return e.Name != W.rFonts && !hpe.Contains(e.Name);
+ }));
+
+ return newMergedElement;
+ }
+
+ private static XName[] TogglePropertyNames = new[] {
+ W.b,
+ W.bCs,
+ W.caps,
+ W.emboss,
+ W.i,
+ W.iCs,
+ W.imprint,
+ W.outline,
+ W.shadow,
+ W.smallCaps,
+ W.strike,
+ W.vanish
+ };
+
+ private static XName[] PropertyNames = new[] {
+ W.cs,
+ W.rtl,
+ W.u,
+ W.color,
+ W.highlight,
+ W.shd
+ };
+
+ public class CharStyleAttributes
+ {
+ public string AsciiFont;
+ public string HAnsiFont;
+ public string EastAsiaFont;
+ public string CsFont;
+ public string Hint;
+ public bool Rtl;
+
+ public string LatinLang;
+ public string BidiLang;
+ public string EastAsiaLang;
+
+ public Dictionary<XName, bool?> ToggleProperties;
+ public Dictionary<XName, XElement> Properties;
+
+ public CharStyleAttributes(XElement pPr, XElement rPr)
+ {
+ ToggleProperties = new Dictionary<XName, bool?>();
+ Properties = new Dictionary<XName, XElement>();
+
+ if (rPr == null)
+ return;
+ foreach (XName xn in TogglePropertyNames)
+ {
+ ToggleProperties[xn] = GetBoolProperty(rPr, xn);
+ }
+ foreach (XName xn in PropertyNames)
+ {
+ Properties[xn] = GetXmlProperty(rPr, xn);
+ }
+ var rFonts = rPr.Element(W.rFonts);
+ if (rFonts == null)
+ {
+ this.AsciiFont = null;
+ this.HAnsiFont = null;
+ this.EastAsiaFont = null;
+ this.CsFont = null;
+ this.Hint = null;
+ }
+ else
+ {
+ this.AsciiFont = (string)(rFonts.Attribute(W.ascii));
+ this.HAnsiFont = (string)(rFonts.Attribute(W.hAnsi));
+ this.EastAsiaFont = (string)(rFonts.Attribute(W.eastAsia));
+ this.CsFont = (string)(rFonts.Attribute(W.cs));
+ this.Hint = (string)(rFonts.Attribute(W.hint));
+ }
+ XElement csel = this.Properties[W.cs];
+ bool cs = csel != null && (csel.Attribute(W.val) == null || csel.Attribute(W.val).ToBoolean() == true);
+ XElement rtlel = this.Properties[W.rtl];
+ bool rtl = rtlel != null && (rtlel.Attribute(W.val) == null || rtlel.Attribute(W.val).ToBoolean() == true);
+ var bidi = false;
+ if (pPr != null)
+ {
+ XElement bidiel = pPr.Element(W.bidi);
+ bidi = bidiel != null && (bidiel.Attribute(W.val) == null || bidiel.Attribute(W.val).ToBoolean() == true);
+ }
+ Rtl = cs || rtl || bidi;
+ var lang = rPr.Element(W.lang);
+ if (lang != null)
+ {
+ LatinLang = (string)lang.Attribute(W.val);
+ BidiLang = (string)lang.Attribute(W.bidi);
+ EastAsiaLang = (string)lang.Attribute(W.eastAsia);
+ }
+ }
+
+ private static bool? GetBoolProperty(XElement rPr, XName propertyName)
+ {
+ if (rPr.Element(propertyName) == null)
+ return null;
+ var s = (string)rPr.Element(propertyName).Attribute(W.val);
+ if (s == null)
+ return true;
+ if (s == "1")
+ return true;
+ if (s == "0")
+ return false;
+ if (s == "true")
+ return true;
+ if (s == "false")
+ return false;
+ if (s == "on")
+ return true;
+ if (s == "off")
+ return false;
+ return (bool)(rPr.Element(propertyName).Attribute(W.val));
+ }
+
+ private static XElement GetXmlProperty(XElement rPr, XName propertyName)
+ {
+ return rPr.Element(propertyName);
+ }
+
+ private static XName[] TogglePropertyNames = new[] {
+ W.b,
+ W.bCs,
+ W.caps,
+ W.emboss,
+ W.i,
+ W.iCs,
+ W.imprint,
+ W.outline,
+ W.shadow,
+ W.smallCaps,
+ W.strike,
+ W.vanish
+ };
+
+ private static XName[] PropertyNames = new[] {
+ W.cs,
+ W.rtl,
+ W.u,
+ W.color,
+ W.highlight,
+ W.shd
+ };
+
+ }
+
+ private static HashSet<char> WeakAndNeutralDirectionalCharacters = new HashSet<char>() {
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '+',
+ '-',
+ ':',
+ ',',
+ '.',
+ '|',
+ '\t',
+ '\r',
+ '\n',
+ ' ',
+ '\x00A0', // non breaking space
+
+ '\x00B0', // degree sign
+ '\x066B', // arabic decimal separator
+ '\x066C', // arabic thousands separator
+
+ '\x0627', // arabic pipe
+
+ '\x20A0', // start currency symbols
+ '\x20A1',
+ '\x20A2',
+ '\x20A3',
+ '\x20A4',
+ '\x20A5',
+ '\x20A6',
+ '\x20A7',
+ '\x20A8',
+ '\x20A9',
+ '\x20AA',
+ '\x20AB',
+ '\x20AC',
+ '\x20AD',
+ '\x20AE',
+ '\x20AF',
+ '\x20B0',
+ '\x20B1',
+ '\x20B2',
+ '\x20B3',
+ '\x20B4',
+ '\x20B5',
+ '\x20B6',
+ '\x20B7',
+ '\x20B8',
+ '\x20B9',
+ '\x20BA',
+ '\x20BB',
+ '\x20BC',
+ '\x20BD',
+ '\x20BE',
+ '\x20BF',
+ '\x20C0',
+ '\x20C1',
+ '\x20C2',
+ '\x20C3',
+ '\x20C4',
+ '\x20C5',
+ '\x20C6',
+ '\x20C7',
+ '\x20C8',
+ '\x20C9',
+ '\x20CA',
+ '\x20CB',
+ '\x20CC',
+ '\x20CD',
+ '\x20CE',
+ '\x20CF', // end currency symbols
+
+ '\x0660', // "Arabic" Indic Numeral Forms Iraq and West
+ '\x0661',
+ '\x0662',
+ '\x0663',
+ '\x0664',
+ '\x0665',
+ '\x0666',
+ '\x0667',
+ '\x0668',
+ '\x0669',
+
+ '\x06F0', // "Arabic" Indic Numberal Forms Iran and East
+ '\x06F1',
+ '\x06F2',
+ '\x06F3',
+ '\x06F4',
+ '\x06F5',
+ '\x06F6',
+ '\x06F7',
+ '\x06F8',
+ '\x06F9',
+ };
+
+ private static void AdjustFontAttributes(WordprocessingDocument wDoc, XElement paraOrRun, XElement pPr,
+ XElement rPr, FormattingAssemblerSettings settings)
+ {
+ XDocument themeXDoc = null;
+ if (wDoc.MainDocumentPart.ThemePart != null)
+ themeXDoc = wDoc.MainDocumentPart.ThemePart.GetXDocument();
+
+ XElement fontScheme = null;
+ XElement majorFont = null;
+ XElement minorFont = null;
+ if (themeXDoc != null)
+ {
+ fontScheme = themeXDoc.Root.Element(A.themeElements).Element(A.fontScheme);
+ majorFont = fontScheme.Element(A.majorFont);
+ minorFont = fontScheme.Element(A.minorFont);
+ }
+ var rFonts = rPr.Element(W.rFonts);
+ if (rFonts == null)
+ {
+ return;
+ }
+ var asciiTheme = (string)rFonts.Attribute(W.asciiTheme);
+ var hAnsiTheme = (string)rFonts.Attribute(W.hAnsiTheme);
+ var eastAsiaTheme = (string)rFonts.Attribute(W.eastAsiaTheme);
+ var cstheme = (string)rFonts.Attribute(W.cstheme);
+ string ascii = null;
+ string hAnsi = null;
+ string eastAsia = null;
+ string cs = null;
+
+ XElement minorLatin = null;
+ string minorLatinTypeface = null;
+ XElement majorLatin = null;
+ string majorLatinTypeface = null;
+
+ if (minorFont != null)
+ {
+ minorLatin = minorFont.Element(A.latin);
+ minorLatinTypeface = (string)minorLatin.Attribute("typeface");
+ }
+
+ if (majorFont != null)
+ {
+ majorLatin = majorFont.Element(A.latin);
+ majorLatinTypeface = (string)majorLatin.Attribute("typeface");
+ }
+ if (asciiTheme != null)
+ {
+ if (asciiTheme.StartsWith("minor") && minorLatinTypeface != null)
+ {
+ ascii = minorLatinTypeface;
+ }
+ else if (asciiTheme.StartsWith("major") && majorLatinTypeface != null)
+ {
+ ascii = majorLatinTypeface;
+ }
+ }
+ if (hAnsiTheme != null)
+ {
+ if (hAnsiTheme.StartsWith("minor") && minorLatinTypeface != null)
+ {
+ hAnsi = minorLatinTypeface;
+ }
+ else if (hAnsiTheme.StartsWith("major") && majorLatinTypeface != null)
+ {
+ hAnsi = majorLatinTypeface;
+ }
+ }
+ if (eastAsiaTheme != null)
+ {
+ if (eastAsiaTheme.StartsWith("minor") && minorLatinTypeface != null)
+ {
+ eastAsia = minorLatinTypeface;
+ }
+ else if (eastAsiaTheme.StartsWith("major") && majorLatinTypeface != null)
+ {
+ eastAsia = majorLatinTypeface;
+ }
+ }
+ if (cstheme != null)
+ {
+ if (cstheme.StartsWith("minor") && minorFont != null)
+ {
+ cs = (string)minorFont.Element(A.cs).Attribute("typeface");
+ }
+ else if (cstheme.StartsWith("major") && majorFont != null)
+ {
+ cs = (string)majorFont.Element(A.cs).Attribute("typeface");
+ }
+ }
+
+ if (ascii != null)
+ {
+ rFonts.SetAttributeValue(W.ascii, ascii);
+ }
+ if (hAnsi != null)
+ {
+ rFonts.SetAttributeValue(W.hAnsi, hAnsi);
+ }
+ if (eastAsia != null)
+ {
+ rFonts.SetAttributeValue(W.eastAsia, eastAsia);
+ }
+ if (cs != null)
+ {
+ rFonts.SetAttributeValue(W.cs, cs);
+ }
+
+ var firstTextNode = paraOrRun.Descendants(W.t).FirstOrDefault(t => t.Value.Length > 0);
+ string str = " ";
+
+ // if there is a run with no text in it, then no need to do any of the rest of this method.
+ if (firstTextNode == null && paraOrRun.Name == W.r)
+ return;
+
+ if (firstTextNode != null)
+ str = firstTextNode.Value;
+
+ var csa = new CharStyleAttributes(pPr, rPr);
+
+ // This module determines the font based on just the first character.
+ // Technically, a run can contain characters from different Unicode code blocks, and hence should be rendered with different fonts.
+ // However, Word breaks up runs that use more than one font into multiple runs. Other producers of WordprocessingML may not, so in
+ // that case, this routine may need to be augmented to look at all characters in a run.
+
+ /*
+ old code
+ var fontFamilies = str.select(function (c) {
+ var ft = Pav.DetermineFontTypeFromCharacter(c, csa);
+ switch (ft) {
+ case Pav.FontType.Ascii:
+ return cast(rFonts.attribute(W.ascii));
+ case Pav.FontType.HAnsi:
+ return cast(rFonts.attribute(W.hAnsi));
+ case Pav.FontType.EastAsia:
+ return cast(rFonts.attribute(W.eastAsia));
+ case Pav.FontType.CS:
+ return cast(rFonts.attribute(W.cs));
+ default:
+ return null;
+ }
+ })
+ .where(function (f) { return f != null && f != ""; })
+ .distinct()
+ .select(function (f) { return new Pav.FontFamily(f); })
+ .toArray();
+ */
+
+ var charToExamine = str.FirstOrDefault(c => ! WeakAndNeutralDirectionalCharacters.Contains(c));
+ if (charToExamine == '\0')
+ charToExamine = str[0];
+
+ var ft = DetermineFontTypeFromCharacter(charToExamine, csa);
+ string fontType = null;
+ string languageType = null;
+ switch (ft)
+ {
+ case FontType.Ascii:
+ fontType = (string)rFonts.Attribute(W.ascii);
+ languageType = "western";
+ break;
+ case FontType.HAnsi:
+ fontType = (string)rFonts.Attribute(W.hAnsi);
+ languageType = "western";
+ break;
+ case FontType.EastAsia:
+ if (settings.RestrictToSupportedLanguages)
+ throw new UnsupportedLanguageException("EastAsia languages are not supported");
+ fontType = (string)rFonts.Attribute(W.eastAsia);
+ languageType = "eastAsia";
+ break;
+ case FontType.CS:
+ if (settings.RestrictToSupportedLanguages)
+ throw new UnsupportedLanguageException("Complex script (RTL) languages are not supported");
+ fontType = (string)rFonts.Attribute(W.cs);
+ languageType = "bidi";
+ break;
+ }
+
+ if (fontType != null)
+ {
+ if (paraOrRun.Attribute(PtOpenXml.FontName) == null)
+ {
+ XAttribute fta = new XAttribute(PtOpenXml.FontName, fontType.ToString());
+ paraOrRun.Add(fta);
+ }
+ else
+ {
+ paraOrRun.Attribute(PtOpenXml.FontName).Value = fontType.ToString();
+ }
+ }
+ if (languageType != null)
+ {
+ if (paraOrRun.Attribute(PtOpenXml.LanguageType) == null)
+ {
+ XAttribute lta = new XAttribute(PtOpenXml.LanguageType, languageType);
+ paraOrRun.Add(lta);
+ }
+ else
+ {
+ paraOrRun.Attribute(PtOpenXml.LanguageType).Value = languageType;
+ }
+ }
+ }
+
+ public enum FontType
+ {
+ Ascii,
+ HAnsi,
+ EastAsia,
+ CS
+ };
+
+ // The algorithm for this method comes from the implementer notes in [MS-OI29500].pdf
+ // section 2.1.87
+
+ // The implementer notes are at:
+ // http://msdn.microsoft.com/en-us/library/ee908652.aspx
+
+ public static FontType DetermineFontTypeFromCharacter(char ch, CharStyleAttributes csa)
+ {
+ // If the run has the cs element ("[ISO/IEC-29500-1] §17.3.2.7; cs") or the rtl element ("[ISO/IEC-29500-1] §17.3.2.30; rtl"),
+ // then the cs (or cstheme if defined) font is used, regardless of the Unicode character values of the run’s content.
+ if (csa.Rtl)
+ {
+ return FontType.CS;
+ }
+
+ // A large percentage of characters will fall in the following rule.
+
+ // Unicode Block: Basic Latin
+ if (ch >= 0x00 && ch <= 0x7f)
+ {
+ return FontType.Ascii;
+ }
+
+ // If the eastAsia (or eastAsiaTheme if defined) attribute’s value is “Times New Roman” and the ascii (or asciiTheme if defined)
+ // and hAnsi (or hAnsiTheme if defined) attributes are equal, then the ascii (or asciiTheme if defined) font is used.
+ if (csa.EastAsiaFont == "Times New Roman" &&
+ csa.AsciiFont == csa.HAnsiFont)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode BLock: Latin-1 Supplement
+ if (ch >= 0xA0 && ch <= 0xFF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (ch == 0xA1 ||
+ ch == 0xA4 ||
+ ch == 0xA7 ||
+ ch == 0xA8 ||
+ ch == 0xAA ||
+ ch == 0xAD ||
+ ch == 0xAF ||
+ (ch >= 0xB0 && ch <= 0xB4) ||
+ (ch >= 0xB6 && ch <= 0xBA) ||
+ (ch >= 0xBC && ch <= 0xBF) ||
+ ch == 0xD7 ||
+ ch == 0xF7)
+ {
+ return FontType.EastAsia;
+ }
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans")
+ {
+ if (ch == 0xE0 ||
+ ch == 0xE1 ||
+ (ch >= 0xE8 && ch <= 0xEA) ||
+ (ch >= 0xEC && ch <= 0xED) ||
+ (ch >= 0xF2 && ch <= 0xF3) ||
+ (ch >= 0xF9 && ch <= 0xFA) ||
+ ch == 0xFC)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Latin Extended-A
+ if (ch >= 0x0100 && ch <= 0x017F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"
+ /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Latin Extended-B
+ if (ch >= 0x0180 && ch <= 0x024F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"
+ /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: IPA Extensions
+ if (ch >= 0x0250 && ch <= 0x02AF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"
+ /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Spacing Modifier Letters
+ if (ch >= 0x02B0 && ch <= 0x02FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Combining Diacritic Marks
+ if (ch >= 0x0300 && ch <= 0x036F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Greek
+ if (ch >= 0x0370 && ch <= 0x03CF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Cyrillic
+ if (ch >= 0x0400 && ch <= 0x04FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Hebrew
+ if (ch >= 0x0590 && ch <= 0x05FF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Arabic
+ if (ch >= 0x0600 && ch <= 0x06FF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Syriac
+ if (ch >= 0x0700 && ch <= 0x074F)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Arabic Supplement
+ if (ch >= 0x0750 && ch <= 0x077F)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Thanna
+ if (ch >= 0x0780 && ch <= 0x07BF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Hangul Jamo
+ if (ch >= 0x1100 && ch <= 0x11FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Latin Extended Additional
+ if (ch >= 0x1E00 && ch <= 0x1EFF)
+ {
+ if (csa.Hint == "eastAsia" &&
+ (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"))
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: General Punctuation
+ if (ch >= 0x2000 && ch <= 0x206F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Superscripts and Subscripts
+ if (ch >= 0x2070 && ch <= 0x209F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Currency Symbols
+ if (ch >= 0x20A0 && ch <= 0x20CF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Combining Diacritical Marks for Symbols
+ if (ch >= 0x20D0 && ch <= 0x20FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Letter-like Symbols
+ if (ch >= 0x2100 && ch <= 0x214F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Number Forms
+ if (ch >= 0x2150 && ch <= 0x218F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Arrows
+ if (ch >= 0x2190 && ch <= 0x21FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Mathematical Operators
+ if (ch >= 0x2200 && ch <= 0x22FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Miscellaneous Technical
+ if (ch >= 0x2300 && ch <= 0x23FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Control Pictures
+ if (ch >= 0x2400 && ch <= 0x243F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Optical Character Recognition
+ if (ch >= 0x2440 && ch <= 0x245F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Enclosed Alphanumerics
+ if (ch >= 0x2460 && ch <= 0x24FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Box Drawing
+ if (ch >= 0x2500 && ch <= 0x257F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Block Elements
+ if (ch >= 0x2580 && ch <= 0x259F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Geometric Shapes
+ if (ch >= 0x25A0 && ch <= 0x25FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Miscellaneous Symbols
+ if (ch >= 0x2600 && ch <= 0x26FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Dingbats
+ if (ch >= 0x2700 && ch <= 0x27BF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: CJK Radicals Supplement
+ if (ch >= 0x2E80 && ch <= 0x2EFF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Kangxi Radicals
+ if (ch >= 0x2F00 && ch <= 0x2FDF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Ideographic Description Characters
+ if (ch >= 0x2FF0 && ch <= 0x2FFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Symbols and Punctuation
+ if (ch >= 0x3000 && ch <= 0x303F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Hiragana
+ if (ch >= 0x3040 && ch <= 0x309F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Katakana
+ if (ch >= 0x30A0 && ch <= 0x30FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Bopomofo
+ if (ch >= 0x3100 && ch <= 0x312F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Hangul Compatibility Jamo
+ if (ch >= 0x3130 && ch <= 0x318F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Kanbun
+ if (ch >= 0x3190 && ch <= 0x319F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Enclosed CJK Letters and Months
+ if (ch >= 0x3200 && ch <= 0x32FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Compatibility
+ if (ch >= 0x3300 && ch <= 0x33FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Unified Ideographs Extension A
+ if (ch >= 0x3400 && ch <= 0x4DBF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Unified Ideographs
+ if (ch >= 0x4E00 && ch <= 0x9FAF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Yi Syllables
+ if (ch >= 0xA000 && ch <= 0xA48F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Yi Radicals
+ if (ch >= 0xA490 && ch <= 0xA4CF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Hangul Syllables
+ if (ch >= 0xAC00 && ch <= 0xD7AF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: High Surrogates
+ if (ch >= 0xD800 && ch <= 0xDB7F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: High Private Use Surrogates
+ if (ch >= 0xDB80 && ch <= 0xDBFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Low Surrogates
+ if (ch >= 0xDC00 && ch <= 0xDFFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Private Use Area
+ if (ch >= 0xE000 && ch <= 0xF8FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: CJK Compatibility Ideographs
+ if (ch >= 0xF900 && ch <= 0xFAFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Alphabetic Presentation Forms
+ if (ch >= 0xFB00 && ch <= 0xFB4F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (ch >= 0xFB00 && ch <= 0xFB1C)
+ return FontType.EastAsia;
+ if (ch >= 0xFB1D && ch <= 0xFB4F)
+ return FontType.Ascii;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Arabic Presentation Forms-A
+ if (ch >= 0xFB50 && ch <= 0xFDFF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: CJK Compatibility Forms
+ if (ch >= 0xFE30 && ch <= 0xFE4F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Small Form Variants
+ if (ch >= 0xFE50 && ch <= 0xFE6F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Arabic Presentation Forms-B
+ if (ch >= 0xFE70 && ch <= 0xFEFE)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Halfwidth and Fullwidth Forms
+ if (ch >= 0xFF00 && ch <= 0xFFEF)
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ private class FormattingAssemblerInfo
+ {
+ public string DefaultParagraphStyleName;
+ public string DefaultCharacterStyleName;
+ public string DefaultTableStyleName;
+ public Dictionary<string, XElement> RolledCharacterStyles;
+ public FormattingAssemblerInfo()
+ {
+ RolledCharacterStyles = new Dictionary<string, XElement>();
+ }
+ }
+
+ // CachedParaInfo is an optimization for the case where a paragraph contains thousands of runs.
+ private class CachedParaInfo
+ {
+ public string ParagraphStyleName;
+ public XElement ParagraphProperties;
+ }
+
+ public class UnsupportedNumberingFormatException : Exception
+ {
+ public UnsupportedNumberingFormatException(string message) : base(message) { }
+ }
+
+ public class UnsupportedLanguageException : Exception
+ {
+ public UnsupportedLanguageException(string message) : base(message) { }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/GetListItemText_Default.cs b/OpenXmlPowerTools/GetListItemText_Default.cs
new file mode 100644
index 0000000..e0757f6
--- /dev/null
+++ b/OpenXmlPowerTools/GetListItemText_Default.cs
@@ -0,0 +1,255 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools
+{
+ class ListItemTextGetter_Default
+ {
+ 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"
+ };
+
+ public static string GetListItemText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ if (numFmt == "none")
+ {
+ return "";
+ }
+ 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")
+ {
+ int levelNumber2 = levelNumber % 780;
+ if (levelNumber2 == 0)
+ levelNumber2 = 780;
+ string a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ int c = (levelNumber2 - 1) / 26;
+ int n = (levelNumber2 - 1) % 26;
+ char x = a[n];
+ return "".PadRight(c + 1, x);
+ }
+ if (numFmt == "lowerLetter")
+ {
+ int levelNumber3 = levelNumber % 780;
+ if (levelNumber3 == 0)
+ levelNumber3 = 780;
+ string a = "abcdefghijklmnopqrstuvwxyz";
+ int c = (levelNumber3 - 1) / 26;
+ int n = (levelNumber3 - 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 == "01, 02, 03, ...")
+ {
+ return string.Format("{0:00}", levelNumber);
+ }
+ if (numFmt == "001, 002, 003, ...")
+ {
+ return string.Format("{0:000}", levelNumber);
+ }
+ if (numFmt == "0001, 0002, 0003, ...")
+ {
+ return string.Format("{0:0000}", levelNumber);
+ }
+ if (numFmt == "00001, 00002, 00003, ...")
+ {
+ return string.Format("{0:00000}", levelNumber);
+ }
+ if (numFmt == "bullet")
+ return "";
+ if (numFmt == "decimalEnclosedCircle")
+ {
+ if (levelNumber >= 1 && levelNumber <= 20)
+ {
+ // 9311 + levelNumber
+ var s = new string(new[] { (char)(9311 + levelNumber) });
+ return s;
+ }
+ return levelNumber.ToString();
+ }
+ return levelNumber.ToString();
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/GetListItemText_fr_FR.cs b/OpenXmlPowerTools/GetListItemText_fr_FR.cs
new file mode 100644
index 0000000..b60c2df
--- /dev/null
+++ b/OpenXmlPowerTools/GetListItemText_fr_FR.cs
@@ -0,0 +1,248 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools
+{
+ public class ListItemTextGetter_fr_FR
+ {
+ private static string[] OneThroughNineteen = {
+ "", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit",
+ "neuf", "dix", "onze", "douze", "treize", "quatorze",
+ "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf"
+ };
+
+ private static string[] Tens = {
+ "", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante-dix",
+ "quatre-vingt", "quatre-vingt-dix"
+ };
+
+ private static string[] OrdinalOneThroughNineteen = {
+ "", "unième", "deuxième", "troisième", "quatrième", "cinquième", "sixième",
+ "septième", "huitième", "neuvième", "dixième", "onzième", "douzième",
+ "treizième", "quatorzième", "quinzième", "seizième",
+ "dix-septième", "dix-huitième", "dix-neuvième"
+ };
+
+ private static string[] OrdinalTenths = {
+ "", "dixième", "vingtième", "trentième", "quarantième", "cinquantième",
+ "soixantième", "soixante-dixième", "quatre-vingtième", "quatre-vingt-dixième"
+ };
+
+ private static string[] OrdinalTenthsPlus = {
+ "", "", "vingt", "trente", "quarante", "cinquante",
+ "soixante", "", "quatre-vingt", ""
+ };
+
+ public static string GetListItemText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ if (numFmt == "cardinalText")
+ {
+ string result = "";
+
+ var sLevel = (levelNumber + 10000).ToString();
+ int thousands = int.Parse(sLevel.Substring(1, 1));
+ int hundreds = int.Parse(sLevel.Substring(2, 1));
+ int tens = int.Parse(sLevel.Substring(3, 1));
+ int ones = int.Parse(sLevel.Substring(4, 1));
+
+ /* exact thousands */
+ if (levelNumber == 1000)
+ return "Mille";
+ if (levelNumber > 1000 && hundreds == 0 && tens == 0 && ones == 0)
+ {
+ result = OneThroughNineteen[thousands] + " mille";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 1000 */
+ if (levelNumber > 1000 && levelNumber < 2000)
+ result = "mille ";
+ else if (levelNumber > 2000 && levelNumber < 10000)
+ result = OneThroughNineteen[thousands] + " mille ";
+
+ /* exact hundreds */
+ if (hundreds > 0 && tens == 0 && ones == 0)
+ {
+ if (hundreds == 1)
+ result += "cent";
+ else
+ result += OneThroughNineteen[hundreds] + " cents";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 100 */
+ if (hundreds > 0)
+ {
+ if (hundreds == 1)
+ result += "cent ";
+ else
+ result += OneThroughNineteen[hundreds] + " cent ";
+ }
+
+ /* exact tens */
+ if (tens > 0 && ones == 0)
+ {
+ if (tens == 8)
+ result += "quatre-vingts";
+ else
+ result += Tens[tens];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* 71-79 */
+ if (tens == 7)
+ {
+ if (ones == 1)
+ result += Tens[6] + " et " + OneThroughNineteen[ones + 10];
+ else
+ result += Tens[6] + "-" + OneThroughNineteen[ones + 10];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* 91-99 */
+ if (tens == 9)
+ {
+ result += Tens[8] + "-" + OneThroughNineteen[ones + 10];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ if (tens >= 2)
+ {
+ if (ones == 1 && tens != 8 && tens != 9)
+ result += Tens[tens] + " et un";
+ else
+ result += Tens[tens] + "-" + OneThroughNineteen[ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ if (levelNumber < 20)
+ {
+ result += OneThroughNineteen[tens * 10 + ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ result += OneThroughNineteen[tens * 10 + ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ if (numFmt == "ordinal")
+ {
+ string suffix;
+ if (levelNumber == 1)
+ suffix = "er";
+ else
+ suffix = "e";
+ return levelNumber.ToString() + suffix;
+ }
+ if (numFmt == "ordinalText")
+ {
+ string result = "";
+
+ if (levelNumber == 1)
+ return "Premier";
+
+ var sLevel = (levelNumber + 10000).ToString();
+ int thousands = int.Parse(sLevel.Substring(1, 1));
+ int hundreds = int.Parse(sLevel.Substring(2, 1));
+ int tens = int.Parse(sLevel.Substring(3, 1));
+ int ones = int.Parse(sLevel.Substring(4, 1));
+
+ /* exact thousands */
+ if (levelNumber == 1000)
+ return "Millième";
+ if (levelNumber > 1000 && hundreds == 0 && tens == 0 && ones == 0)
+ {
+ result = OneThroughNineteen[thousands] + " millième";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 1000 */
+ if (levelNumber > 1000 && levelNumber < 2000)
+ result = "mille ";
+ else if (levelNumber > 2000 && levelNumber < 10000)
+ result = OneThroughNineteen[thousands] + " mille ";
+
+ /* exact hundreds */
+ if (hundreds > 0 && tens == 0 && ones == 0)
+ {
+ if (hundreds == 1)
+ result += "centième";
+ else
+ result += OneThroughNineteen[hundreds] + " centième";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 100 */
+ if (hundreds > 0)
+ {
+ if (hundreds == 1)
+ result += "cent ";
+ else
+ result += OneThroughNineteen[hundreds] + " cent ";
+ }
+
+ /* exact tens */
+ if (tens > 0 && ones == 0)
+ {
+ result += OrdinalTenths[tens];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* 71-79 */
+ if (tens == 7)
+ {
+ if (ones == 1)
+ result += OrdinalTenthsPlus[6] + " et " + OrdinalOneThroughNineteen[ones + 10];
+ else
+ result += OrdinalTenthsPlus[6] + "-" + OrdinalOneThroughNineteen[ones + 10];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* 91-99 */
+ if (tens == 9)
+ {
+ result += OrdinalTenthsPlus[8] + "-" + OrdinalOneThroughNineteen[ones + 10];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ if (tens >= 2)
+ {
+ if (ones == 1 && tens != 8 && tens != 9)
+ result += OrdinalTenthsPlus[tens] + " et unième";
+ else
+ result += OrdinalTenthsPlus[tens] + "-" + OrdinalOneThroughNineteen[ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ if (levelNumber < 20)
+ {
+ result += OrdinalOneThroughNineteen[tens * 10 + ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ result += OrdinalOneThroughNineteen[tens * 10 + ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ return null;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/GetListItemText_ru_RU.cs b/OpenXmlPowerTools/GetListItemText_ru_RU.cs
new file mode 100644
index 0000000..016f17e
--- /dev/null
+++ b/OpenXmlPowerTools/GetListItemText_ru_RU.cs
@@ -0,0 +1,137 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools
+{
+ public class ListItemTextGetter_ru_RU
+ {
+ private static string[] OneThroughNineteen = {
+ "один", "два", "три", "четыре", "пять", "шесть", "семь", "восемь",
+ "девять", "десять", "одиннадцать", "двенадцать", "тринадцать", "четырнадцать",
+ "пятнадцать", "шестнадцать", "семнадцать", "восемнадцать", "девятнадцать"
+ };
+
+ private static string[] Tens = {
+ "десять", "двадцать", "тридцать", "сорок", "пятьдесят", "шестьдесят", "семьдесят",
+ "восемьдесят", "девяносто"
+ };
+
+ private static string[] OrdinalOneThroughNineteen = {
+ "первый", "второй", "третий", "четвертый", "пятый", "шестой",
+ "седьмой", "восьмой", "девятый", "десятый", "одиннадцатый", "двенадцатый",
+ "тринадцатый", "четырнадцатый", "пятнадцатый", "шестнадцатый",
+ "семнадцатый", "восемнадцатый", "девятнадцатый"
+ };
+
+ private static string[] OrdinalTenths = {
+ "десятый", "двадцатый", "тридцатый", "сороковой", "пятидесятый",
+ "шестидесятый", "семидесятый", "восьмидесятый", "девяностый"
+ };
+
+ // TODO this is not correct for values above 99
+
+ public static string GetListItemText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ 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);
+ }
+ return null;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/GetListItemText_sv_SE.cs b/OpenXmlPowerTools/GetListItemText_sv_SE.cs
new file mode 100644
index 0000000..9f6d4a0
--- /dev/null
+++ b/OpenXmlPowerTools/GetListItemText_sv_SE.cs
@@ -0,0 +1,228 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools
+{
+ public class ListItemTextGetter_sv_SE
+ {
+ private static string[] OneThroughNineteen = {
+ "", "ett", "två", "tre", "fyra", "fem", "sex", "sju", "åtta",
+ "nio", "tio", "elva", "tolv", "tretton", "fjorton",
+ "femton", "sexton", "sjutton", "arton", "nitton"
+ };
+
+ private static string[] Tens = {
+ "","tio", "tjugo", "trettio", "fyrtio", "femtio", "sextio", "sjuttio", "åttio",
+ "nittio", "etthundra"
+ };
+
+ private static string[] OrdinalOneThroughNineteen = {
+ "", "första", "andra", "tredje", "fjärde", "femte", "sjätte", "sjunde",
+ "åttonde", "nionde", "tionde", "elfte", "tolfte", "trettonde",
+ "fjortonde", "femtonde", "sextonde", "sjuttonde",
+ "artonde", "nittonde"
+ };
+
+ public static string GetListItemText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ switch (numFmt)
+ {
+ case "cardinalText":
+ return NumberAsCardinalText(languageCultureName, levelNumber, numFmt);
+ case "ordinalText":
+ return NumberAsOrdinalText(languageCultureName, levelNumber, numFmt);
+ case "ordinal":
+ return NumberAsOrdinal(languageCultureName, levelNumber, numFmt);
+ default:
+ return null;
+ }
+ }
+ private static string NumberAsCardinalText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ string result = "";
+
+ var sLevel = (levelNumber + 10000).ToString();
+ int thousands = int.Parse(sLevel.Substring(1, 1));
+ int hundreds = int.Parse(sLevel.Substring(2, 1));
+ int tens = int.Parse(sLevel.Substring(3, 1));
+ int ones = int.Parse(sLevel.Substring(4, 1));
+
+
+ //Validation
+ if (thousands > 19)
+ throw new ArgumentOutOfRangeException("levelNumber", "Convering a levelNumber to ordinal text that is greater then 19 999 is not supported");
+ if (levelNumber == 0)
+ return "Noll";
+ if (levelNumber < 0)
+ throw new ArgumentOutOfRangeException("levelNumber", "Converting a negative levelNumber to ordinal text is not supported");
+
+ /* exact thousands */
+ if (levelNumber == 1000)
+ return "Ettusen";
+ if (levelNumber > 1000 && hundreds == 0 && tens == 0 && ones == 0)
+ {
+ result = OneThroughNineteen[thousands] + "tusen";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 1000 */
+ if (levelNumber > 1000 && levelNumber < 2000)
+ result = "ettusen";
+ else if (levelNumber > 2000 && levelNumber < 10000)
+ result = OneThroughNineteen[thousands] + "tusen";
+
+ /* exact hundreds */
+ if (hundreds > 0 && tens == 0 && ones == 0)
+ {
+ if (hundreds == 1)
+ result += "etthundra";
+ else
+ result += OneThroughNineteen[hundreds] + "hundra";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 100 */
+ if (hundreds > 0)
+ {
+ if (hundreds == 1)
+ result += "etthundra";
+ else
+ result += OneThroughNineteen[hundreds] + "hundra";
+ }
+
+ /* exact tens */
+ if (tens > 0 && ones == 0)
+ {
+ result += Tens[tens];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 20 */
+ if (tens == 1)
+ {
+ result += OneThroughNineteen[tens * 10 + ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ else if (tens > 1)
+ {
+ result += Tens[tens] + OneThroughNineteen[ones]; ;
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ else
+ {
+ result += OneThroughNineteen[ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ }
+ private static string NumberAsOrdinalText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ string result = "";
+
+ if (levelNumber <= 0)
+ throw new ArgumentOutOfRangeException("levelNumber", "Converting a zero or negative levelNumber to ordinal text is not supported");
+ if(levelNumber >= 10000)
+ throw new ArgumentOutOfRangeException("levelNumber", "Convering a levelNumber to ordinal text that is greater then 10000 is not supported");
+
+ if (levelNumber == 1)
+ return "Första";
+
+ var sLevel = (levelNumber + 10000).ToString();
+ int thousands = int.Parse(sLevel.Substring(1, 1));
+ int hundreds = int.Parse(sLevel.Substring(2, 1));
+ int tens = int.Parse(sLevel.Substring(3, 1));
+ int ones = int.Parse(sLevel.Substring(4, 1));
+
+
+ /* exact thousands */
+ if (levelNumber == 1000)
+ return "Ettusende";
+ if (levelNumber > 1000 && hundreds == 0 && tens == 0 && ones == 0)
+ {
+ result = OneThroughNineteen[thousands] + "tusende";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 1000 */
+ if (levelNumber > 1000 && levelNumber < 2000)
+ result = "ettusen";
+ else if (levelNumber > 2000 && levelNumber < 10000)
+ result = OneThroughNineteen[thousands] + "tusende";
+
+ /* exact hundreds */
+ if (hundreds > 0 && tens == 0 && ones == 0)
+ {
+ if (hundreds == 1)
+ result += "etthundrade";
+ else
+ result += OneThroughNineteen[hundreds] + "hundrade";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 100 */
+ if (hundreds > 0)
+ {
+ result += OneThroughNineteen[hundreds] + "hundra";
+ }
+
+ /* exact tens */
+ if (tens > 0 && ones == 0)
+ {
+ result += Tens[tens] + "nde";
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+
+ /* > 20 */
+ if (tens == 1)
+ {
+ result += OrdinalOneThroughNineteen[tens * 10 + ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ else if (tens > 1)
+ {
+ result += Tens[tens] + OrdinalOneThroughNineteen[ones]; ;
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ else
+ {
+ result += OrdinalOneThroughNineteen[ones];
+ return result.Substring(0, 1).ToUpper() + result.Substring(1);
+ }
+ }
+ private static string NumberAsOrdinal(string languageCultureName, int levelNumber, string numFmt)
+ {
+ string levelAsString = levelNumber.ToString();
+
+ if (levelAsString == null)
+ return "";
+ if (levelAsString.Trim() == "")
+ return "";
+
+ if(levelAsString.EndsWith("1"))
+ return levelAsString + ":a";
+ else if(levelAsString.EndsWith("2"))
+ return levelAsString + ":a";
+ else
+ return levelAsString + ":e";
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/GetListItemText_tr_TR.cs b/OpenXmlPowerTools/GetListItemText_tr_TR.cs
new file mode 100644
index 0000000..155bb88
--- /dev/null
+++ b/OpenXmlPowerTools/GetListItemText_tr_TR.cs
@@ -0,0 +1,234 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Ayberk CAL
+Coordinator: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools
+{
+ public class ListItemTextGetter_tr_TR
+ {
+ 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 = {
+ "bir", "iki", "üç", "dört", "beş", "altı", "yedi", "sekiz",
+ "dokuz", "on", "onbir", "oniki", "onüç", "ondört",
+ "onbeş", "onaltı", "onyedi", "onsekiz", "ondokuz"
+ };
+
+ private static string[] Tens = {
+ "on", "yirmi", "otuz", "kırk", "elli", "altmış", "yetmiş",
+ "seksen", "doksan"
+ };
+
+ private static string[] OrdinalOneThroughNineteen = {
+ "birinci", "ikinci", "üçüncü", "dördüncü", "beşinci", "altıncı",
+ "yedinci", "sekizinci", "dokuzuncu", "onuncu", "onbirinci", "onikinci",
+ "onüçüncü", "ondördüncü", "onbeşinci", "onaltıncı",
+ "onyedinci", "onsekizinci", "ondokuzuncu"
+ };
+
+ private static string[] TwoThroughNineteen = {
+ "", "iki", "üç", "dört", "beş", "altı", "yedi", "sekiz",
+ "dokuz", "on", "onbir", "oniki", "onüç", "ondört",
+ "onbeş", "onaltı", "onyedi", "onsekiz", "ondokuz"
+ };
+
+ private static string[] OrdinalTenths = {
+ "onuncu", "yirminci", "otuzuncu", "kırkıncı", "ellinci",
+ "altmışıncı", "yetmişinci", "sekseninci", "doksanıncı"
+ };
+
+ public static string GetListItemText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ #region
+ 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 = "ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ";
+ //string a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ int c = (levelNumber - 1) / 29;
+ int n = (levelNumber - 1) % 29;
+ char x = a[n];
+ return "".PadRight(c + 1, x);
+ }
+ if (numFmt == "lowerLetter")
+ {
+ string a = "abcçdefgğhıijklmnoöprsştuüvyz";
+ int c = (levelNumber - 1) / 29;
+ int n = (levelNumber - 1) % 29;
+ 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";*/
+ suffix = ".";
+ return levelNumber.ToString() + suffix;
+ }
+ if (numFmt == "cardinalText")
+ {
+ string result = "";
+ int t1 = levelNumber / 1000;
+ int t2 = levelNumber % 1000;
+ if (t1 >= 1)
+ result += OneThroughNineteen[t1 - 1] + " yüz";
+ 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] + " bin";
+ 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);
+ }
+ #endregion
+ if (numFmt == "ordinalText")
+ {
+ string result = "";
+ int t1 = levelNumber / 1000;
+ int t2 = levelNumber % 1000;
+ if (t1 >= 1 && t2 != 0)
+ result += TwoThroughNineteen[t1 - 1] + "bin";
+ if (t1 >= 1 && t2 == 0)
+ {
+ result += TwoThroughNineteen[t1 - 1] + "bininci";
+ 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 += TwoThroughNineteen[h1 - 1] + "yüz";
+ if (h1 >= 1 && h2 == 0)
+ {
+ result += TwoThroughNineteen[h1 - 1] + "yüzüncü";
+ 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]; //result += "-" + OrdinalOneThroughNineteen[r - 1];
+ }
+ return result.Substring(0, 1).ToUpper() +
+ result.Substring(1);
+ }
+ if (numFmt == "0001, 0002, 0003, ...")
+ {
+ return string.Format("{0:0000}", levelNumber);
+ }
+ if (numFmt == "bullet")
+ return "";
+ return levelNumber.ToString();
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/GetListItemText_zh_CN.cs b/OpenXmlPowerTools/GetListItemText_zh_CN.cs
new file mode 100644
index 0000000..142bf84
--- /dev/null
+++ b/OpenXmlPowerTools/GetListItemText_zh_CN.cs
@@ -0,0 +1,145 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools
+{
+ public class ListItemTextGetter_zh_CN
+ {
+ public static string GetListItemText(string languageCultureName, int levelNumber, string numFmt)
+ {
+ string[] ccTDigitCharacters = new[] {
+ "",
+ "一",
+ "二",
+ "三",
+ "四",
+ "五",
+ "六",
+ "七",
+ "八",
+ "九",
+ };
+ string tenCharacter = "十";
+ string hundredCharacter = "百";
+ string thousandCharacter = "千";
+ string andCharacter = "〇";
+
+ string[] ccDigitCharacters = new[] {
+ "○",
+ "一",
+ "二",
+ "三",
+ "四",
+ "五",
+ "六",
+ "七",
+ "八",
+ "九",
+ };
+
+ int thousandsRemainder = levelNumber % 1000;
+ int hundredsRemainder = levelNumber % 100;
+ int thousands = levelNumber / 1000;
+ int hundreds = (levelNumber % 1000) / 100;
+ int tens = (levelNumber % 100) / 10;
+ int ones = levelNumber % 10;
+
+ if (numFmt == "chineseCounting")
+ {
+ if (levelNumber >= 1 && levelNumber <= 9)
+ return ccDigitCharacters[levelNumber];
+ if (levelNumber >= 10 && levelNumber <= 19)
+ {
+ if (levelNumber == 10)
+ return tenCharacter;
+ return tenCharacter + ccDigitCharacters[ones];
+ }
+ if (levelNumber >= 11 && levelNumber <= 99)
+ {
+ if (ones == 0)
+ return ccDigitCharacters[tens] + tenCharacter;
+ return ccDigitCharacters[tens] + tenCharacter + ccDigitCharacters[ones];
+ }
+ if (levelNumber >= 100 && levelNumber <= 999)
+ return ccDigitCharacters[hundreds] + ccDigitCharacters[tens] + ccDigitCharacters[ones];
+ if (levelNumber >= 1000 && levelNumber <= 9999)
+ return ccDigitCharacters[thousands] + ccDigitCharacters[hundreds] + ccDigitCharacters[tens] + ccDigitCharacters[ones];
+ return levelNumber.ToString();
+ }
+ if (numFmt == "chineseCountingThousand")
+ {
+ if (levelNumber >= 1 && levelNumber <= 9)
+ return ccTDigitCharacters[levelNumber];
+ if (levelNumber >= 10 && levelNumber <= 19)
+ return tenCharacter + ccTDigitCharacters[ones];
+ if (levelNumber >= 20 && levelNumber <= 99)
+ return ccTDigitCharacters[tens] + tenCharacter + ccTDigitCharacters[ones];
+ if (levelNumber >= 100 && levelNumber <= 999)
+ {
+ if (hundredsRemainder == 0)
+ return ccTDigitCharacters[hundreds] + hundredCharacter;
+ if (hundredsRemainder >= 1 && hundredsRemainder <= 9)
+ return ccTDigitCharacters[hundreds] + hundredCharacter + andCharacter + ccTDigitCharacters[levelNumber % 10];
+ if (ones == 0)
+ return ccTDigitCharacters[hundreds] + hundredCharacter + ccTDigitCharacters[tens] + tenCharacter;
+ return ccTDigitCharacters[hundreds] + hundredCharacter + ccTDigitCharacters[tens] + tenCharacter + ccTDigitCharacters[ones];
+ }
+ if (levelNumber >= 1000 && levelNumber <= 9999)
+ {
+ if (thousandsRemainder == 0)
+ return ccTDigitCharacters[thousands] + thousandCharacter;
+ if (thousandsRemainder >= 1 && thousandsRemainder <= 9)
+ return ccTDigitCharacters[thousands] + thousandCharacter + andCharacter + GetListItemText("zh_CN", thousandsRemainder, numFmt);
+ if (thousandsRemainder >= 10 && thousandsRemainder <= 99)
+ return ccTDigitCharacters[thousands] + thousandCharacter + andCharacter + ccTDigitCharacters[tens] + tenCharacter + ccTDigitCharacters[ones];
+ if (hundredsRemainder == 0)
+ return ccTDigitCharacters[thousands] + thousandCharacter + ccTDigitCharacters[hundreds] + hundredCharacter;
+ if (hundredsRemainder >= 1 && hundredsRemainder <= 9)
+ return ccTDigitCharacters[thousands] + thousandCharacter + ccTDigitCharacters[hundreds] + hundredCharacter + andCharacter + ccTDigitCharacters[ones];
+ return ccTDigitCharacters[thousands] + thousandCharacter + ccTDigitCharacters[hundreds] + hundredCharacter + ccTDigitCharacters[tens] + tenCharacter + ccTDigitCharacters[ones];
+ }
+ return levelNumber.ToString();
+ }
+ if (numFmt == "ideographTraditional")
+ {
+ string[] iDigitCharacters = new[] {
+ " ",
+ "甲",
+ "乙",
+ "丙",
+ "丁",
+ "戊",
+ "己",
+ "庚",
+ "辛",
+ "壬",
+ "癸",
+ };
+ if (levelNumber >= 1 && levelNumber <= 10)
+ return iDigitCharacters[levelNumber];
+ return levelNumber.ToString();
+ }
+ return null;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/HtmlToWmlConverter.cs b/OpenXmlPowerTools/HtmlToWmlConverter.cs
new file mode 100644
index 0000000..a947a83
--- /dev/null
+++ b/OpenXmlPowerTools/HtmlToWmlConverter.cs
@@ -0,0 +1,549 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using OpenXmlPowerTools.HtmlToWml;
+using OpenXmlPowerTools.HtmlToWml.CSS;
+using System.Text.RegularExpressions;
+using System.Windows.Forms;
+
+namespace OpenXmlPowerTools
+{
+ public class HtmlToWmlConverterSettings
+ {
+ public string MajorLatinFont;
+ public string MinorLatinFont;
+ public double DefaultFontSize;
+ public XElement DefaultSpacingElement;
+ public XElement DefaultSpacingElementForParagraphsInTables;
+ public XElement SectPr;
+ public string DefaultBlockContentMargin;
+ public string BaseUriForImages;
+
+ public Twip PageWidthTwips { get { return (long)SectPr.Elements(W.pgSz).Attributes(W._w).FirstOrDefault(); } }
+ public Twip PageMarginLeftTwips { get { return (long)SectPr.Elements(W.pgMar).Attributes(W.left).FirstOrDefault(); } }
+ public Twip PageMarginRightTwips { get { return (long)SectPr.Elements(W.pgMar).Attributes(W.right).FirstOrDefault(); } }
+ public Emu PageWidthEmus { get { return Emu.TwipsToEmus(PageWidthTwips); } }
+ public Emu PageMarginLeftEmus { get { return Emu.TwipsToEmus(PageMarginLeftTwips); } }
+ public Emu PageMarginRightEmus { get { return Emu.TwipsToEmus(PageMarginRightTwips); } }
+ }
+
+ public class HtmlToWmlConverter
+ {
+ public static WmlDocument ConvertHtmlToWml(
+ string defaultCss,
+ string authorCss,
+ string userCss,
+ XElement xhtml,
+ HtmlToWmlConverterSettings settings)
+ {
+ return HtmlToWmlConverterCore.ConvertHtmlToWml(defaultCss, authorCss, userCss, xhtml, settings, null, null);
+ }
+
+ public static WmlDocument ConvertHtmlToWml(
+ string defaultCss,
+ string authorCss,
+ string userCss,
+ XElement xhtml,
+ HtmlToWmlConverterSettings settings,
+ WmlDocument emptyDocument,
+ string annotatedHtmlDumpFileName)
+ {
+ return HtmlToWmlConverterCore.ConvertHtmlToWml(defaultCss, authorCss, userCss, xhtml, settings, emptyDocument, annotatedHtmlDumpFileName);
+ }
+
+ private static string s_Blank_wml_base64 = @"UEsDBBQABgAIAAAAIQAJJIeCgQEAAI4FAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0
+lE1Pg0AQhu8m/geyVwPbejDGlPag9ahNrPG8LkPZyH5kZ/v17x1KS6qhpVq9kMAy7/vMCzOD0UqX
+0QI8KmtS1k96LAIjbabMLGWv08f4lkUYhMlEaQ2kbA3IRsPLi8F07QAjqjaYsiIEd8c5ygK0wMQ6
+MHSSW69FoFs/407IDzEDft3r3XBpTQAT4lBpsOHgAXIxL0M0XtHjmsRDiSy6r1+svFImnCuVFIFI
++cJk31zirUNClZt3sFAOrwiD8VaH6uSwwbbumaLxKoNoInx4Epow+NL6jGdWzjX1kByXaeG0ea4k
+NPWVmvNWAiJlrsukOdFCmR3/QQ4M6xLw7ylq3RPt31QoxnkOkj52dx4a46rppLbYq+12gxAopFNM
+vv6CcVfouFXuRFjC+8u/UeyJd4LkNBpT8V7CCYn/MIxGuhMi0LwD31z7Z3NsZI5Z0mRMvHVI+8P/
+ou3dgqiqYxo5Bz4oaFZE24g1jrR7zu4Pqu2WQdbizTfbdPgJAAD//wMAUEsDBBQABgAIAAAAIQAe
+kRq38wAAAE4CAAALAAgCX3JlbHMvLnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjJLbSgNBDIbvBd9hyH032woi0tneSKF3
+IusDhJnsAXcOzKTavr2jILpQ217m9OfLT9abg5vUO6c8Bq9hWdWg2JtgR99reG23iwdQWchbmoJn
+DUfOsGlub9YvPJGUoTyMMaui4rOGQSQ+ImYzsKNchci+VLqQHEkJU4+RzBv1jKu6vsf0VwOamaba
+WQ1pZ+9AtcdYNl/WDl03Gn4KZu/Yy4kVyAdhb9kuYipsScZyjWop9SwabDDPJZ2RYqwKNuBpotX1
+RP9fi46FLAmhCYnP83x1nANaXg902aJ5x687HyFZLBZ9e/tDg7MvaD4BAAD//wMAUEsDBBQABgAI
+AAAAIQB8O5c5IgEAALkDAAAcAAgBd29yZC9fcmVscy9kb2N1bWVudC54bWwucmVscyCiBAEooAAB
+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";
+
+ private static WmlDocument s_EmptyDocument = null;
+
+ public static WmlDocument EmptyDocument
+ {
+ get {
+ if (s_EmptyDocument == null)
+ {
+ s_EmptyDocument = new WmlDocument("EmptyDocument.docx", Convert.FromBase64String(s_Blank_wml_base64));
+ }
+ return s_EmptyDocument;
+ }
+ }
+
+ public static HtmlToWmlConverterSettings GetDefaultSettings()
+ {
+ return GetDefaultSettings(EmptyDocument);
+ }
+
+ public static HtmlToWmlConverterSettings GetDefaultSettings(WmlDocument wmlDocument)
+ {
+ HtmlToWmlConverterSettings settings = new HtmlToWmlConverterSettings();
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlDocument.DocumentByteArray, 0, wmlDocument.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, false))
+ {
+ string majorLatinFont, minorLatinFont;
+ double defaultFontSize;
+ GetDefaultFontInfo(wDoc, out majorLatinFont, out minorLatinFont, out defaultFontSize);
+ settings.MajorLatinFont = majorLatinFont;
+ settings.MinorLatinFont = minorLatinFont;
+ settings.DefaultFontSize = defaultFontSize;
+
+ settings.MinorLatinFont = "Times New Roman";
+ settings.DefaultFontSize = 12d;
+ settings.DefaultBlockContentMargin = "auto";
+ settings.DefaultSpacingElement = new XElement(W.spacing,
+ new XAttribute(W.before, 100),
+ new XAttribute(W.beforeAutospacing, 1),
+ new XAttribute(W.after, 100),
+ new XAttribute(W.afterAutospacing, 1),
+ new XAttribute(W.line, 240),
+ new XAttribute(W.lineRule, "auto"));
+ settings.DefaultSpacingElementForParagraphsInTables = new XElement(W.spacing,
+ new XAttribute(W.before, 100),
+ new XAttribute(W.beforeAutospacing, 1),
+ new XAttribute(W.after, 100),
+ new XAttribute(W.afterAutospacing, 1),
+ new XAttribute(W.line, 240),
+ new XAttribute(W.lineRule, "auto"));
+
+ XDocument mXDoc = wDoc.MainDocumentPart.GetXDocument();
+ XElement existingSectPr = mXDoc.Root.Descendants(W.sectPr).FirstOrDefault();
+ settings.SectPr = new XElement(W.sectPr,
+ existingSectPr.Elements(W.pgSz),
+ existingSectPr.Elements(W.pgMar));
+ }
+ }
+ return settings;
+ }
+
+ private static void GetDefaultFontInfo(WordprocessingDocument wDoc, out string majorLatinFont, out string minorLatinFont, out double defaultFontSize)
+ {
+ if (wDoc.MainDocumentPart.ThemePart != null)
+ {
+ XElement fontScheme = wDoc.MainDocumentPart.ThemePart.GetXDocument().Root.Elements(A.themeElements).Elements(A.fontScheme).FirstOrDefault();
+ if (fontScheme != null)
+ {
+ majorLatinFont = (string)fontScheme.Elements(A.majorFont).Elements(A.latin).Attributes(NoNamespace.typeface).FirstOrDefault();
+ minorLatinFont = (string)fontScheme.Elements(A.minorFont).Elements(A.latin).Attributes(NoNamespace.typeface).FirstOrDefault();
+ string defaultFontSizeString = (string)wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument().Root.Elements(W.docDefaults)
+ .Elements(W.rPrDefault).Elements(W.rPr).Elements(W.sz).Attributes(W.val).FirstOrDefault();
+ if (defaultFontSizeString != null)
+ {
+ double dfs;
+ if (double.TryParse(defaultFontSizeString, out dfs))
+ {
+ defaultFontSize = dfs / 2d;
+ return;
+ }
+ defaultFontSize = 12;
+ return;
+ }
+ }
+ }
+ majorLatinFont = "";
+ minorLatinFont = "";
+ defaultFontSize = 12;
+ }
+
+ public static string CleanUpCss(string css)
+ {
+ if (css == null)
+ return "";
+ css = css.Trim();
+ string cleanCss = Regex.Split(css, "\r\n|\r|\n")
+ .Where(l =>
+ {
+ string lTrim = l.Trim();
+ if (lTrim == "//")
+ return false;
+ if (lTrim == "////")
+ return false;
+ if (lTrim == "<!--" || lTrim == "<!--")
+ return false;
+ if (lTrim == "-->" || lTrim == "-->")
+ return false;
+ return true;
+ })
+ .Select(l => l + Environment.NewLine )
+ .StringConcatenate();
+ return cleanCss;
+ }
+ }
+
+ public struct Emu
+ {
+ public long m_Value;
+ public static int s_EmusPerInch = 914400;
+
+ public static Emu TwipsToEmus(long twips)
+ {
+ float v1 = (float)twips / 20f;
+ float v2 = v1 / 72f;
+ float v3 = v2 * s_EmusPerInch;
+ long emus = (long)v3;
+ return new Emu(emus);
+ }
+
+ public static Emu PointsToEmus(double points)
+ {
+ double v1 = points / 72;
+ double v2 = v1 * s_EmusPerInch;
+ long emus = (long)v2;
+ return new Emu(emus);
+ }
+
+ public Emu(long value)
+ {
+ m_Value = value;
+ }
+
+ public static implicit operator long(Emu e)
+ {
+ return e.m_Value;
+ }
+
+ public static implicit operator Emu(long l)
+ {
+ return new Emu(l);
+ }
+
+ public override string ToString()
+ {
+ throw new OpenXmlPowerToolsException("Can't convert directly to string, must cast to long");
+ }
+ }
+
+ public struct TPoint
+ {
+ public double m_Value;
+
+ public TPoint(double value)
+ {
+ m_Value = value;
+ }
+
+ public static implicit operator double(TPoint t)
+ {
+ return t.m_Value;
+ }
+
+ public static implicit operator TPoint(double d)
+ {
+ return new TPoint(d);
+ }
+
+ public override string ToString()
+ {
+ throw new OpenXmlPowerToolsException("Can't convert directly to string, must cast to double");
+ }
+ }
+
+ public struct Twip
+ {
+ public long m_Value;
+
+ public Twip(long value)
+ {
+ m_Value = value;
+ }
+
+ public static implicit operator long(Twip t)
+ {
+ return t.m_Value;
+ }
+
+ public static implicit operator Twip(long l)
+ {
+ return new Twip(l);
+ }
+
+ public static implicit operator Twip(double d)
+ {
+ return new Twip((long)d);
+ }
+
+ public override string ToString()
+ {
+ throw new OpenXmlPowerToolsException("Can't convert directly to string, must cast to long");
+ }
+ }
+
+ public class SizeEmu
+ {
+ public Emu m_Height;
+ public Emu m_Width;
+
+ public SizeEmu(Emu width, Emu height)
+ {
+ m_Width = width;
+ m_Height = height;
+ }
+
+ public SizeEmu(long width, long height)
+ {
+ m_Width = width;
+ m_Height = height;
+ }
+ }
+}
+
diff --git a/OpenXmlPowerTools/HtmlToWmlConverterCore.cs b/OpenXmlPowerTools/HtmlToWmlConverterCore.cs
new file mode 100644
index 0000000..8c35e21
--- /dev/null
+++ b/OpenXmlPowerTools/HtmlToWmlConverterCore.cs
@@ -0,0 +1,5433 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+/***************************************************************************
+ * HTML elements handled in this module:
+ *
+ * a
+ * b
+ * body
+ * caption
+ * div
+ * em
+ * h1, h2, h3, h4, h5, h6, h7, h8
+ * hr
+ * html
+ * i
+ * blockquote
+ * img
+ * li
+ * ol
+ * p
+ * s
+ * span
+ * strong
+ * style
+ * sub
+ * sup
+ * table
+ * tbody
+ * td
+ * th
+ * tr
+ * u
+ * ul
+ * br
+ * tt
+ * code
+ * kbd
+ * samp
+ * pre
+ *
+ * HTML elements that are handled by recursively processing descedants
+ *
+ * article
+ * hgroup
+ * nav
+ * section
+ * dd
+ * dl
+ * dt
+ * figure
+ * main
+ * abbr
+ * bdi
+ * bdo
+ * cite
+ * data
+ * dfn
+ * mark
+ * q
+ * rp
+ * rt
+ * ruby
+ * small
+ * time
+ * var
+ * wbr
+ *
+ * HTML elements ignored in this module
+ *
+ * head
+ *
+***************************************************************************/
+
+// need to research all of the html attributes that take effect, such as border="1" and somehow work into the rendering system.
+// note that some of these 'inherit' so need to implement correct logic.
+
+// this module has not been fully engineered to work with RTL languages. This is a pending work item. There are issues involved,
+// including that there is RTL content in HTML that can't be represented in WordprocessingML, although this probably is rare.
+// The reverse is not true - all RTL WordprocessingML can be represented in HTML, but there is some HTML RTL content that can only
+// be approximated in WordprocessingML.
+
+// have I handled all forms of colors? see GetWmlColorFromExpression in HtmlToWmlCssApplier
+
+// min-height and max-height not implemented yet.
+
+// internal hyperlinks are not supported. I believe it possible - bookmarks can be created, hyperlinks to the bookmark can be created.
+
+// To be supported in future
+// page-break-before:always
+
+// ************************************************************************
+// ToDo at some point in the future
+// I'm not implementing caption exactly correctly. If caption does not have borders, then there needs to not be a border around the table,
+// otherwise it looks as if caption has a border. See T1200. If there is, per the markup, a table border, but caption does not have a border,
+// then need to make sure that all of the cells below the caption have the border on the appropriate sides so that it looks as if the table
+// has a border.
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using OpenXmlPowerTools.HtmlToWml;
+using OpenXmlPowerTools.HtmlToWml.CSS;
+using System.Text.RegularExpressions;
+using System.Windows.Forms;
+
+namespace OpenXmlPowerTools.HtmlToWml
+{
+ public class ElementToStyleMap
+ {
+ public string ElementName;
+ public string StyleName;
+ }
+
+ public static class LocalExtensions
+ {
+ public static CssExpression GetProp(this XElement element, string propertyName)
+ {
+ Dictionary<string, CssExpression> d = element.Annotation<Dictionary<string, CssExpression>>();
+ if (d != null)
+ {
+ if (d.ContainsKey(propertyName))
+ return d[propertyName];
+ }
+ return null;
+ }
+ }
+
+ public class HtmlToWmlConverterCore
+ {
+ public static WmlDocument ConvertHtmlToWml(
+ string defaultCss,
+ string authorCss,
+ string userCss,
+ XElement xhtml,
+ HtmlToWmlConverterSettings settings)
+ {
+ return ConvertHtmlToWml(defaultCss, authorCss, userCss, xhtml, settings, null, null);
+ }
+
+ public static WmlDocument ConvertHtmlToWml(
+ string defaultCss,
+ string authorCss,
+ string userCss,
+ XElement xhtml,
+ HtmlToWmlConverterSettings settings,
+ WmlDocument emptyDocument,
+ string annotatedHtmlDumpFileName)
+ {
+ if (emptyDocument == null)
+ emptyDocument = HtmlToWmlConverter.EmptyDocument;
+
+ NextRectId = 1025;
+
+ // clone and transform all element names to lower case
+ XElement html = (XElement)TransformToLower(xhtml);
+
+ // add pseudo cells for rowspan
+ html = (XElement)AddPseudoCells(html);
+
+ html = (XElement)TransformWhiteSpaceInPreCodeTtKbdSamp(html, false, false);
+
+ CssDocument defaultCssDoc, userCssDoc, authorCssDoc;
+ CssApplier.ApplyAllCss(
+ defaultCss,
+ authorCss,
+ userCss,
+ html,
+ settings,
+ out defaultCssDoc,
+ out authorCssDoc,
+ out userCssDoc,
+ annotatedHtmlDumpFileName);
+
+ WmlDocument newWmlDocument;
+
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(emptyDocument))
+ {
+ using (WordprocessingDocument wDoc = streamDoc.GetWordprocessingDocument())
+ {
+ AnnotateOlUl(wDoc, html);
+ UpdateMainDocumentPart(wDoc, html, settings);
+ NormalizeMainDocumentPart(wDoc);
+ StylesUpdater.UpdateStylesPart(wDoc, html, settings, defaultCssDoc, authorCssDoc, userCssDoc);
+ HtmlToWmlFontUpdater.UpdateFontsPart(wDoc, html, settings);
+ ThemeUpdater.UpdateThemePart(wDoc, html, settings);
+ NumberingUpdater.UpdateNumberingPart(wDoc, html, settings);
+ }
+ newWmlDocument = streamDoc.GetModifiedWmlDocument();
+ }
+
+ return newWmlDocument;
+ }
+
+
+ private static object TransformToLower(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ XElement e = new XElement(element.Name.LocalName.ToLower(),
+ element.Attributes().Select(a => new XAttribute(a.Name.LocalName.ToLower(), a.Value)),
+ element.Nodes().Select(n => TransformToLower(n)));
+ return e;
+ }
+ return node;
+ }
+
+ private static XElement AddPseudoCells(XElement html)
+ {
+ while (true)
+ {
+ var rowSpanCell = html
+ .Descendants(XhtmlNoNamespace.td)
+ .FirstOrDefault(td => td.Attribute(XhtmlNoNamespace.rowspan) != null && td.Attribute("HtmlToWmlVMergeRestart") == null);
+ if (rowSpanCell == null)
+ break;
+ rowSpanCell.Add(
+ new XAttribute("HtmlToWmlVMergeRestart", "true"));
+ int colNumber = rowSpanCell.ElementsBeforeSelf(XhtmlNoNamespace.td).Count();
+ int numberPseudoToAdd = (int)rowSpanCell.Attribute(XhtmlNoNamespace.rowspan) - 1;
+ var tr = rowSpanCell.Ancestors(XhtmlNoNamespace.tr).FirstOrDefault();
+ if (tr == null)
+ throw new OpenXmlPowerToolsException("Invalid HTML - td does not have parent tr");
+ var rowsToAddTo = tr
+ .ElementsAfterSelf(XhtmlNoNamespace.tr)
+ .Take(numberPseudoToAdd)
+ .ToList();
+ foreach (var rowToAddTo in rowsToAddTo)
+ {
+ if (colNumber > 0)
+ {
+ var tdToAddAfter = rowToAddTo
+ .Elements(XhtmlNoNamespace.td)
+ .Skip(colNumber - 1)
+ .FirstOrDefault();
+ var td = new XElement(XhtmlNoNamespace.td,
+ rowSpanCell.Attributes(),
+ new XAttribute("HtmlToWmlVMergeNoRestart", "true"));
+ tdToAddAfter.AddAfterSelf(td);
+ }
+ else
+ {
+ var tdToAddBefore = rowToAddTo
+ .Elements(XhtmlNoNamespace.td)
+ .Skip(colNumber)
+ .FirstOrDefault();
+ var td = new XElement(XhtmlNoNamespace.td,
+ rowSpanCell.Attributes(),
+ new XAttribute("HtmlToWmlVMergeNoRestart", "true"));
+ tdToAddBefore.AddBeforeSelf(td);
+ }
+ }
+ }
+ return html;
+ }
+
+ public class NumberedItemAnnotation
+ {
+ public int numId;
+ public int ilvl;
+ public string listStyleType;
+ }
+
+ private static void AnnotateOlUl(WordprocessingDocument wDoc, XElement html)
+ {
+ int numId;
+ NumberingUpdater.GetNextNumId(wDoc, out numId);
+ foreach (var item in html.DescendantsAndSelf().Where(d => d.Name == XhtmlNoNamespace.ol || d.Name == XhtmlNoNamespace.ul))
+ {
+ XElement parentOlUl = item.Ancestors().Where(a => a.Name == XhtmlNoNamespace.ol || a.Name == XhtmlNoNamespace.ul).LastOrDefault();
+ int numIdToUse;
+ if (parentOlUl != null)
+ numIdToUse = parentOlUl.Annotation<NumberedItemAnnotation>().numId;
+ else
+ numIdToUse = numId++;
+ string lst = CssApplier.GetComputedPropertyValue(null, item, "list-style-type", null).ToString();
+ item.AddAnnotation(new NumberedItemAnnotation
+ {
+ numId = numIdToUse,
+ ilvl = item.Ancestors().Where(a => a.Name == XhtmlNoNamespace.ol || a.Name == XhtmlNoNamespace.ul).Count(),
+ listStyleType = lst,
+ });
+ }
+ }
+
+ private static void UpdateMainDocumentPart(WordprocessingDocument wDoc, XElement html, HtmlToWmlConverterSettings settings)
+ {
+ XDocument xDoc = XDocument.Parse(
+@"<w:document xmlns:wpc='http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas'
+ xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
+ xmlns:o='urn:schemas-microsoft-com:office:office'
+ xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships'
+ xmlns:m='http://schemas.openxmlformats.org/officeDocument/2006/math'
+ xmlns:v='urn:schemas-microsoft-com:vml'
+ xmlns:wp14='http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing'
+ xmlns:wp='http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing'
+ xmlns:w10='urn:schemas-microsoft-com:office:word'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'
+ xmlns:w14='http://schemas.microsoft.com/office/word/2010/wordml'
+ xmlns:wpg='http://schemas.microsoft.com/office/word/2010/wordprocessingGroup'
+ xmlns:wpi='http://schemas.microsoft.com/office/word/2010/wordprocessingInk'
+ xmlns:wne='http://schemas.microsoft.com/office/word/2006/wordml'
+ xmlns:wps='http://schemas.microsoft.com/office/word/2010/wordprocessingShape'
+ mc:Ignorable='w14 wp14'/>");
+
+ XElement body = new XElement(W.body,
+ Transform(html, settings, wDoc, NextExpected.Paragraph, false),
+ settings.SectPr);
+
+ AddNonBreakingSpacesForSpansWithWidth(wDoc, body);
+ body = (XElement)TransformAndOrderElements(body);
+
+ foreach (var d in body.Descendants())
+ d.Attributes().Where(a => a.Name.Namespace == PtOpenXml.pt).Remove();
+ xDoc.Root.Add(body);
+ wDoc.MainDocumentPart.PutXDocument(xDoc);
+ }
+
+ private static object TransformWhiteSpaceInPreCodeTtKbdSamp(XNode node, bool inPre, bool inOther)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == XhtmlNoNamespace.pre)
+ {
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformWhiteSpaceInPreCodeTtKbdSamp(n, true, false)));
+ }
+ if (element.Name == XhtmlNoNamespace.code ||
+ element.Name == XhtmlNoNamespace.tt ||
+ element.Name == XhtmlNoNamespace.kbd ||
+ element.Name == XhtmlNoNamespace.samp)
+ {
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformWhiteSpaceInPreCodeTtKbdSamp(n, false, true)));
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformWhiteSpaceInPreCodeTtKbdSamp(n, false, false)));
+ }
+ XText xt = node as XText;
+ if (xt != null && inPre)
+ {
+ var val = xt.Value.TrimStart('\r', '\n').TrimEnd('\r', '\n');
+ var groupedCharacters = val.GroupAdjacent(c => c == '\r' || c == '\n');
+ var newNodes = groupedCharacters.Select(g =>
+ {
+ if (g.Key == true)
+ return (object)(new XElement(XhtmlNoNamespace.br));
+ string x = g.Select(c => c.ToString()).StringConcatenate();
+ return new XText(x);
+ });
+ return newNodes;
+ }
+ if (xt != null && inOther)
+ {
+ var val = xt.Value.TrimStart('\r', '\n', '\t', ' ').TrimEnd('\r', '\n', '\t', ' ');
+ return new XText(val);
+ }
+ return node;
+ }
+
+ private static Dictionary<XName, int> Order_pPr = new Dictionary<XName, int>
+ {
+ { W.pStyle, 10 },
+ { W.keepNext, 20 },
+ { W.keepLines, 30 },
+ { W.pageBreakBefore, 40 },
+ { W.framePr, 50 },
+ { W.widowControl, 60 },
+ { W.numPr, 70 },
+ { W.suppressLineNumbers, 80 },
+ { W.pBdr, 90 },
+ { W.shd, 100 },
+ { W.tabs, 120 },
+ { W.suppressAutoHyphens, 130 },
+ { W.kinsoku, 140 },
+ { W.wordWrap, 150 },
+ { W.overflowPunct, 160 },
+ { W.topLinePunct, 170 },
+ { W.autoSpaceDE, 180 },
+ { W.autoSpaceDN, 190 },
+ { W.bidi, 200 },
+ { W.adjustRightInd, 210 },
+ { W.snapToGrid, 220 },
+ { W.spacing, 230 },
+ { W.ind, 240 },
+ { W.contextualSpacing, 250 },
+ { W.mirrorIndents, 260 },
+ { W.suppressOverlap, 270 },
+ { W.jc, 280 },
+ { W.textDirection, 290 },
+ { W.textAlignment, 300 },
+ { W.textboxTightWrap, 310 },
+ { W.outlineLvl, 320 },
+ { W.divId, 330 },
+ { W.cnfStyle, 340 },
+ { W.rPr, 350 },
+ { W.sectPr, 360 },
+ { W.pPrChange, 370 },
+ };
+
+ private static Dictionary<XName, int> Order_rPr = new Dictionary<XName, int>
+ {
+ { W.ins, 10 },
+ { W.del, 20 },
+ { W.rStyle, 30 },
+ { W.rFonts, 40 },
+ { W.b, 50 },
+ { W.bCs, 60 },
+ { W.i, 70 },
+ { W.iCs, 80 },
+ { W.caps, 90 },
+ { W.smallCaps, 100 },
+ { W.strike, 110 },
+ { W.dstrike, 120 },
+ { W.outline, 130 },
+ { W.shadow, 140 },
+ { W.emboss, 150 },
+ { W.imprint, 160 },
+ { W.noProof, 170 },
+ { W.snapToGrid, 180 },
+ { W.vanish, 190 },
+ { W.webHidden, 200 },
+ { W.color, 210 },
+ { W.spacing, 220 },
+ { W._w, 230 },
+ { W.kern, 240 },
+ { W.position, 250 },
+ { W.sz, 260 },
+ { W14.wShadow, 270 },
+ { W14.wTextOutline, 280 },
+ { W14.wTextFill, 290 },
+ { W14.wScene3d, 300 },
+ { W14.wProps3d, 310 },
+ { W.szCs, 320 },
+ { W.highlight, 330 },
+ { W.u, 340 },
+ { W.effect, 350 },
+ { W.bdr, 360 },
+ { W.shd, 370 },
+ { W.fitText, 380 },
+ { W.vertAlign, 390 },
+ { W.rtl, 400 },
+ { W.cs, 410 },
+ { W.em, 420 },
+ { W.lang, 430 },
+ { W.eastAsianLayout, 440 },
+ { W.specVanish, 450 },
+ { W.oMath, 460 },
+ };
+
+ private static Dictionary<XName, int> Order_tblPr = new Dictionary<XName, int>
+ {
+ { W.tblStyle, 10 },
+ { W.tblpPr, 20 },
+ { W.tblOverlap, 30 },
+ { W.bidiVisual, 40 },
+ { W.tblStyleRowBandSize, 50 },
+ { W.tblStyleColBandSize, 60 },
+ { W.tblW, 70 },
+ { W.jc, 80 },
+ { W.tblCellSpacing, 90 },
+ { W.tblInd, 100 },
+ { W.tblBorders, 110 },
+ { W.shd, 120 },
+ { W.tblLayout, 130 },
+ { W.tblCellMar, 140 },
+ { W.tblLook, 150 },
+ { W.tblCaption, 160 },
+ { W.tblDescription, 170 },
+ };
+
+ private static Dictionary<XName, int> Order_tblBorders = new Dictionary<XName, int>
+ {
+ { W.top, 10 },
+ { W.left, 20 },
+ { W.start, 30 },
+ { W.bottom, 40 },
+ { W.right, 50 },
+ { W.end, 60 },
+ { W.insideH, 70 },
+ { W.insideV, 80 },
+ };
+
+ private static Dictionary<XName, int> Order_tcPr = new Dictionary<XName, int>
+ {
+ { W.cnfStyle, 10 },
+ { W.tcW, 20 },
+ { W.gridSpan, 30 },
+ { W.hMerge, 40 },
+ { W.vMerge, 50 },
+ { W.tcBorders, 60 },
+ { W.shd, 70 },
+ { W.noWrap, 80 },
+ { W.tcMar, 90 },
+ { W.textDirection, 100 },
+ { W.tcFitText, 110 },
+ { W.vAlign, 120 },
+ { W.hideMark, 130 },
+ { W.headers, 140 },
+ };
+
+ private static Dictionary<XName, int> Order_tcBorders = new Dictionary<XName, int>
+ {
+ { W.top, 10 },
+ { W.start, 20 },
+ { W.left, 30 },
+ { W.bottom, 40 },
+ { W.right, 50 },
+ { W.end, 60 },
+ { W.insideH, 70 },
+ { W.insideV, 80 },
+ { W.tl2br, 90 },
+ { W.tr2bl, 100 },
+ };
+
+ private static Dictionary<XName, int> Order_pBdr = new Dictionary<XName, int>
+ {
+ { W.top, 10 },
+ { W.left, 20 },
+ { W.bottom, 30 },
+ { W.right, 40 },
+ { W.between, 50 },
+ { W.bar, 60 },
+ };
+
+ private static object TransformAndOrderElements(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.pPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_pPr.ContainsKey(e.Name))
+ return Order_pPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.rPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_rPr.ContainsKey(e.Name))
+ return Order_rPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tblPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_tblPr.ContainsKey(e.Name))
+ return Order_tblPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tcPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_tcPr.ContainsKey(e.Name))
+ return Order_tcPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tcBorders)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_tcBorders.ContainsKey(e.Name))
+ return Order_tcBorders[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tblBorders)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_tblBorders.ContainsKey(e.Name))
+ return Order_tblBorders[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.pBdr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)TransformAndOrderElements(e)).OrderBy(e =>
+ {
+ if (Order_pBdr.ContainsKey(e.Name))
+ return Order_pBdr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.p)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements(W.pPr).Select(e => (XElement)TransformAndOrderElements(e)),
+ element.Elements().Where(e => e.Name != W.pPr).Select(e => (XElement)TransformAndOrderElements(e)));
+
+ if (element.Name == W.r)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements(W.rPr).Select(e => (XElement)TransformAndOrderElements(e)),
+ element.Elements().Where(e => e.Name != W.rPr).Select(e => (XElement)TransformAndOrderElements(e)));
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformAndOrderElements(n)));
+ }
+ return node;
+ }
+
+ private static void AddNonBreakingSpacesForSpansWithWidth(WordprocessingDocument wDoc, XElement body)
+ {
+ List<XElement> runsWithWidth = body
+ .Descendants(W.r)
+ .Where(r => r.Attribute(PtOpenXml.HtmlToWmlCssWidth) != null)
+ .ToList();
+ foreach (XElement run in runsWithWidth)
+ {
+ XElement p = run.Ancestors(W.p).FirstOrDefault();
+ XElement pPr = p != null ? p.Element(W.pPr) : null;
+ XElement rPr = run.Element(W.rPr);
+ XElement rFonts = rPr != null ? rPr.Element(W.rFonts) : null;
+ string str = run.Descendants(W.t).Select(t => (string) t).StringConcatenate();
+ if ((pPr == null) || (rPr == null) || (rFonts == null) || (str == "")) continue;
+
+ AdjustFontAttributes(wDoc, run, pPr, rPr);
+ var csa = new CharStyleAttributes(pPr, rPr);
+ char charToExamine = str.FirstOrDefault(c => !WeakAndNeutralDirectionalCharacters.Contains(c));
+ if (charToExamine == '\0')
+ charToExamine = str[0];
+
+ FontType ft = DetermineFontTypeFromCharacter(charToExamine, csa);
+ string fontType = null;
+ string languageType = null;
+ switch (ft)
+ {
+ case FontType.Ascii:
+ fontType = (string) rFonts.Attribute(W.ascii);
+ languageType = "western";
+ break;
+ case FontType.HAnsi:
+ fontType = (string) rFonts.Attribute(W.hAnsi);
+ languageType = "western";
+ break;
+ case FontType.EastAsia:
+ fontType = (string) rFonts.Attribute(W.eastAsia);
+ languageType = "eastAsia";
+ break;
+ case FontType.CS:
+ fontType = (string) rFonts.Attribute(W.cs);
+ languageType = "bidi";
+ break;
+ }
+
+ if (fontType != null)
+ {
+ XAttribute fontNameAttribute = run.Attribute(PtOpenXml.FontName);
+ if (fontNameAttribute == null)
+ run.Add(new XAttribute(PtOpenXml.FontName, fontType));
+ else
+ fontNameAttribute.SetValue(fontType);
+ }
+
+ if (languageType != null)
+ {
+ XAttribute languageTypeAttribute = run.Attribute(PtOpenXml.LanguageType);
+ if (languageTypeAttribute == null)
+ {
+ run.Add(new XAttribute(PtOpenXml.LanguageType, languageType));
+ }
+ else
+ {
+ languageTypeAttribute.SetValue(languageType);
+ }
+ }
+
+ int pixWidth = CalcWidthOfRunInPixels(run) ?? 0;
+
+ // calc width of non breaking spaces
+ var npSpRun = new XElement(W.r,
+ run.Attributes(),
+ run.Elements(W.rPr),
+ new XElement(W.t, "\u00a0"));
+ int nbSpWidth = CalcWidthOfRunInPixels(npSpRun) ?? 0;
+ if (nbSpWidth == 0)
+ continue;
+
+ // get HtmlToWmlCssWidth attribute
+ var cssWidth = (string) run.Attribute(PtOpenXml.HtmlToWmlCssWidth);
+ if (!cssWidth.EndsWith("pt")) continue;
+
+ cssWidth = cssWidth.Substring(0, cssWidth.Length - 2);
+ decimal cssWidthInDecimal;
+ if (!decimal.TryParse(cssWidth, out cssWidthInDecimal)) continue;
+
+ // calculate the number of non-breaking spaces to add
+ decimal cssWidthInPixels = cssWidthInDecimal/72*96;
+ var numberOfNpSpToAdd = (int) ((cssWidthInPixels - pixWidth)/nbSpWidth);
+ if (numberOfNpSpToAdd > 0)
+ run.Add(new XElement(W.t, "".PadRight(numberOfNpSpToAdd, '\u00a0')));
+ }
+ }
+
+ private static void NormalizeMainDocumentPart(WordprocessingDocument wDoc)
+ {
+ XDocument mainXDoc = wDoc.MainDocumentPart.GetXDocument();
+ XElement newRoot = (XElement)NormalizeTransform(mainXDoc.Root);
+ mainXDoc.Root.ReplaceWith(newRoot);
+ wDoc.MainDocumentPart.PutXDocument();
+ }
+
+ private static object NormalizeTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p && element.Elements().Any(c => c.Name == W.p || c.Name == W.tbl))
+ {
+ var groupedChildren = element.Elements()
+ .GroupAdjacent(e => e.Name == W.p || e.Name == W.tbl);
+ var newContent = groupedChildren
+ .Select(g =>
+ {
+ if (g.Key == false)
+ {
+ XElement paragraph = new XElement(W.p,
+ element.Elements(W.pPr),
+ g.Where(gc => gc.Name != W.pPr));
+ return (object)paragraph;
+ }
+ return g.Select(n => NormalizeTransform(n));
+ });
+ return newContent;
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => NormalizeTransform(n)));
+ }
+ return node;
+ }
+
+ private enum NextExpected
+ {
+ Paragraph,
+ Run,
+ SubRun,
+ }
+
+ private static object Transform(XNode node, HtmlToWmlConverterSettings settings, WordprocessingDocument wDoc, NextExpected nextExpected, bool preserveWhiteSpace)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == XhtmlNoNamespace.a)
+ {
+ string rId = "R" + Guid.NewGuid().ToString().Replace("-", "");
+ string href = (string)element.Attribute(NoNamespace.href);
+ if (href != null)
+ {
+ Uri uri = null;
+ try
+ {
+ uri = new Uri(href);
+ }
+ catch (UriFormatException)
+ {
+ XElement rPr = GetRunProperties(element, settings);
+ XElement run = new XElement(W.r,
+ rPr,
+ new XElement(W.t, element.Value));
+ return new[] { run };
+ }
+
+ if (uri != null)
+ {
+ wDoc.MainDocumentPart.AddHyperlinkRelationship(uri, true, rId);
+ if (element.Element(XhtmlNoNamespace.img) != null)
+ {
+ var imageTransformed = element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace)).OfType<XElement>();
+ var newImageTransformed = imageTransformed
+ .Select(i =>
+ {
+ if (i.Elements(W.drawing).Any())
+ {
+ var newRun = new XElement(i);
+ var docPr = newRun.Elements(W.drawing).Elements(WP.inline).Elements(WP.docPr).FirstOrDefault();
+ if (docPr != null)
+ {
+ var hlinkClick = new XElement(A.hlinkClick,
+ new XAttribute(R.id, rId),
+ new XAttribute(XNamespace.Xmlns + "a", A.a.NamespaceName));
+ docPr.Add(hlinkClick);
+ }
+ return newRun;
+ }
+ return i;
+ })
+ .ToList();
+ return newImageTransformed;
+ }
+
+ XElement rPr = GetRunProperties(element, settings);
+ XElement hyperlink = new XElement(W.hyperlink,
+ new XAttribute(R.id, rId),
+ new XElement(W.r,
+ rPr,
+ new XElement(W.t, element.Value)));
+ return new[] { hyperlink };
+ }
+ }
+ return null;
+ }
+
+ if (element.Name == XhtmlNoNamespace.b)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.body)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.caption)
+ {
+ return new XElement(W.tr,
+ GetTableRowProperties(element),
+ new XElement(W.tc,
+ GetCellPropertiesForCaption(element),
+ element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Paragraph, preserveWhiteSpace))));
+ }
+
+ if (element.Name == XhtmlNoNamespace.div)
+ {
+ if (nextExpected == NextExpected.Paragraph)
+ {
+ if (element.Descendants().Any(d => d.Name == XhtmlNoNamespace.h1 ||
+ d.Name == XhtmlNoNamespace.li ||
+ d.Name == XhtmlNoNamespace.p ||
+ d.Name == XhtmlNoNamespace.table))
+ {
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+ }
+ else
+ {
+ return GenerateNextExpected(element, settings, wDoc, null, nextExpected, false);
+ }
+ }
+ else
+ {
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+ }
+ }
+
+ if (element.Name == XhtmlNoNamespace.em)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Run, preserveWhiteSpace));
+
+ HeadingInfo hi = HeadingTagMap.FirstOrDefault(htm => htm.Name == element.Name);
+ if (hi != null)
+ {
+ return GenerateNextExpected(element, settings, wDoc, hi.StyleName, NextExpected.Paragraph, false);
+ }
+
+ if (element.Name == XhtmlNoNamespace.hr)
+ {
+ int i = GetNextRectId();
+ XElement hr = XElement.Parse(
+ @"<w:p xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'
+ xmlns:o='urn:schemas-microsoft-com:office:office'
+ xmlns:w14='http://schemas.microsoft.com/office/word/2010/wordml'
+ xmlns:v='urn:schemas-microsoft-com:vml'>
+ <w:r>
+ <w:pict w14:anchorId='0DBC9ADE'>
+ <v:rect id='_x0000_i" + i + @"'
+ style='width:0;height:1.5pt'
+ o:hralign='center'
+ o:hrstd='t'
+ o:hr='t'
+ fillcolor='#a0a0a0'
+ stroked='f'/>
+ </w:pict>
+ </w:r>
+ </w:p>");
+ hr.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
+ return hr;
+ }
+
+ if (element.Name == XhtmlNoNamespace.html)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Paragraph, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.i)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.blockquote)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.img)
+ {
+ if (element.Parent.Name == XhtmlNoNamespace.body)
+ {
+ XElement para = new XElement(W.p,
+ GetParagraphPropertiesForImage(),
+ TransformImageToWml(element, settings, wDoc));
+ return para;
+ }
+ else
+ {
+ XElement content = TransformImageToWml(element, settings, wDoc);
+ return content;
+ }
+ }
+
+ if (element.Name == XhtmlNoNamespace.li)
+ {
+ return GenerateNextExpected(element, settings, wDoc, null, NextExpected.Paragraph, false);
+ }
+
+ if (element.Name == XhtmlNoNamespace.ol)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Paragraph, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.p)
+ {
+ return GenerateNextExpected(element, settings, wDoc, null, NextExpected.Paragraph, false);
+ }
+
+ if (element.Name == XhtmlNoNamespace.s)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ /****************************************** SharePoint Specific ********************************************/
+ // todo sharepoint specific
+ //if (element.Name == Xhtml.div && (string)element.Attribute(Xhtml._class) == "ms-rteElement-Callout1")
+ //{
+ // return new XElement(W.p,
+ // // todo need a style for class
+ // new XElement(W.pPr,
+ // new XElement(W.pStyle,
+ // new XAttribute(W.val, "ms-rteElement-Callout1"))),
+ // new XElement(W.r,
+ // new XElement(W.t, element.Value)));
+ //}
+ if (element.Name == XhtmlNoNamespace.span && (string)element.Attribute(XhtmlNoNamespace.id) == "layoutsData")
+ return null;
+ /****************************************** End SharePoint Specific ********************************************/
+
+ if (element.Name == XhtmlNoNamespace.span)
+ {
+ var spanReplacement = element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+ var dummyElement = new XElement("dummy", spanReplacement);
+ var firstChild = dummyElement.Elements().FirstOrDefault();
+ XElement run = null;
+ if (firstChild != null && firstChild.Name == W.r)
+ run = firstChild;
+ if (run != null)
+ {
+ Dictionary<string, CssExpression> computedProperties = element.Annotation<Dictionary<string, CssExpression>>();
+ if (computedProperties != null && computedProperties.ContainsKey("width"))
+ {
+ string width = computedProperties["width"];
+ if (width != "auto")
+ run.Add(new XAttribute(PtOpenXml.HtmlToWmlCssWidth, width));
+ var rFontsLocal = run.Element(W.rFonts);
+ XElement rFontsGlobal = null;
+ var styleDefPart = wDoc.MainDocumentPart.StyleDefinitionsPart;
+ if (styleDefPart != null)
+ {
+ rFontsGlobal = styleDefPart.GetXDocument().Root.Elements(W.docDefaults).Elements(W.rPrDefault).Elements(W.rPr).Elements(W.rFonts).FirstOrDefault();
+ }
+ var rFontsNew = FontMerge(rFontsLocal, rFontsGlobal);
+ var rPr = run.Element(W.rPr);
+ if (rPr != null)
+ {
+ var rFontsExisting = rPr.Element(W.rFonts);
+ if (rFontsExisting == null)
+ rPr.AddFirst(rFontsGlobal);
+ else
+ rFontsExisting.ReplaceWith(rFontsGlobal);
+ }
+ }
+ return dummyElement.Elements();
+ }
+
+ return spanReplacement;
+ }
+
+ if (element.Name == XhtmlNoNamespace.strong)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.style)
+ return null;
+
+ if (element.Name == XhtmlNoNamespace.sub)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.sup)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.table)
+ {
+ XElement wmlTable = new XElement(W.tbl,
+ GetTableProperties(element),
+ GetTableGrid(element, settings),
+ element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Paragraph, preserveWhiteSpace)));
+ return wmlTable;
+ }
+
+ if (element.Name == XhtmlNoNamespace.tbody)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Paragraph, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.td)
+ {
+ var tdText = element.DescendantNodes().OfType<XText>().Select(t => t.Value).StringConcatenate().Trim();
+ var hasOtherThanSpansAndParas = element.Descendants().Any(d => d.Name != XhtmlNoNamespace.span && d.Name != XhtmlNoNamespace.p);
+ if (tdText != "" || hasOtherThanSpansAndParas)
+ {
+ var newElementRaw = new XElement("dummy", element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Paragraph, preserveWhiteSpace)));
+ var newElements = new XElement("dummy",
+ newElementRaw
+ .Elements()
+ .Select(e =>
+ {
+ if (e.Name == W.hyperlink || e.Name == W.r)
+ return new XElement(W.p, e);
+ return e;
+ }));
+
+ return new XElement(W.tc,
+ GetCellProperties(element),
+ newElements.Elements());
+ }
+ else
+ {
+ XElement p;
+ p = new XElement(W.p,
+ GetParagraphProperties(element, null, settings),
+ new XElement(W.r,
+ GetRunProperties(element, settings),
+ new XElement(W.t, "")));
+ return new XElement(W.tc,
+ GetCellProperties(element), p);
+ }
+ }
+
+ if (element.Name == XhtmlNoNamespace.th)
+ {
+ return new XElement(W.tc,
+ GetCellHeaderProperties(element),
+ element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace)));
+ }
+
+ if (element.Name == XhtmlNoNamespace.tr)
+ {
+ return new XElement(W.tr,
+ GetTableRowProperties(element),
+ element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace)));
+ }
+
+ if (element.Name == XhtmlNoNamespace.u)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.ul)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.br)
+ if (nextExpected == NextExpected.Paragraph)
+ {
+ return new XElement(W.p,
+ new XElement(W.r,
+ new XElement(W.t)));
+ }
+ else
+ {
+ return new XElement(W.r, new XElement(W.br));
+ }
+
+ if (element.Name == XhtmlNoNamespace.tt || element.Name == XhtmlNoNamespace.code || element.Name == XhtmlNoNamespace.kbd || element.Name == XhtmlNoNamespace.samp)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+
+ if (element.Name == XhtmlNoNamespace.pre)
+ return GenerateNextExpected(element, settings, wDoc, null, NextExpected.Paragraph, true);
+
+ // if no match up to this point, then just recursively process descendants
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+ }
+
+ if (node.Parent.Name != XhtmlNoNamespace.title)
+ return GenerateNextExpected(node, settings, wDoc, null, nextExpected, preserveWhiteSpace);
+
+ return null;
+
+ }
+
+ private static XElement FontMerge(XElement higherPriorityFont, XElement lowerPriorityFont)
+ {
+ XElement rFonts;
+
+ if (higherPriorityFont == null)
+ return lowerPriorityFont;
+ if (lowerPriorityFont == null)
+ return higherPriorityFont;
+ if (higherPriorityFont == null && lowerPriorityFont == null)
+ return null;
+
+ rFonts = new XElement(W.rFonts,
+ (higherPriorityFont.Attribute(W.ascii) != null || higherPriorityFont.Attribute(W.asciiTheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.ascii), higherPriorityFont.Attribute(W.asciiTheme) } :
+ new[] { lowerPriorityFont.Attribute(W.ascii), lowerPriorityFont.Attribute(W.asciiTheme) },
+ (higherPriorityFont.Attribute(W.hAnsi) != null || higherPriorityFont.Attribute(W.hAnsiTheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.hAnsi), higherPriorityFont.Attribute(W.hAnsiTheme) } :
+ new[] { lowerPriorityFont.Attribute(W.hAnsi), lowerPriorityFont.Attribute(W.hAnsiTheme) },
+ (higherPriorityFont.Attribute(W.eastAsia) != null || higherPriorityFont.Attribute(W.eastAsiaTheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.eastAsia), higherPriorityFont.Attribute(W.eastAsiaTheme) } :
+ new[] { lowerPriorityFont.Attribute(W.eastAsia), lowerPriorityFont.Attribute(W.eastAsiaTheme) },
+ (higherPriorityFont.Attribute(W.cs) != null || higherPriorityFont.Attribute(W.cstheme) != null) ?
+ new[] { higherPriorityFont.Attribute(W.cs), higherPriorityFont.Attribute(W.cstheme) } :
+ new[] { lowerPriorityFont.Attribute(W.cs), lowerPriorityFont.Attribute(W.cstheme) },
+ (higherPriorityFont.Attribute(W.hint) != null ? higherPriorityFont.Attribute(W.hint) :
+ lowerPriorityFont.Attribute(W.hint))
+ );
+
+ return rFonts;
+ }
+
+ private static int? CalcWidthOfRunInPixels(XElement r)
+ {
+ var fontName = (string)r.Attribute(PtOpenXml.FontName) ??
+ (string)r.Ancestors(W.p).First().Attribute(PtOpenXml.FontName);
+ if (fontName == null)
+ throw new OpenXmlPowerToolsException("Internal Error, should have FontName attribute");
+ if (UnknownFonts.Contains(fontName))
+ return 0;
+
+ if (UnknownFonts.Contains(fontName))
+ return null;
+
+ var rPr = r.Element(W.rPr);
+ if (rPr == null)
+ return null;
+
+ var sz = GetFontSize(r) ?? 22m;
+
+ // unknown font families will throw ArgumentException, in which case just return 0
+ if (!KnownFamilies.Contains(fontName))
+ return 0;
+
+ // in theory, all unknown fonts are found by the above test, but if not...
+ FontFamily ff;
+ try
+ {
+ ff = new FontFamily(fontName);
+ }
+ catch (ArgumentException)
+ {
+ UnknownFonts.Add(fontName);
+
+ return 0;
+ }
+
+ var fs = FontStyle.Regular;
+ if (Util.GetBoolProp(rPr, W.b) == true || Util.GetBoolProp(rPr, W.bCs) == true)
+ fs |= FontStyle.Bold;
+ if (Util.GetBoolProp(rPr, W.i) == true || Util.GetBoolProp(rPr, W.iCs) == true)
+ fs |= FontStyle.Italic;
+
+ // Appended blank as a quick fix to accommodate that will get
+ // appended to some layout-critical runs such as list item numbers.
+ // In some cases, this might not be required or even wrong, so this
+ // must be revisited.
+ // TODO: Revisit.
+ var runText = r.DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate() + " ";
+
+ var tabLength = r.DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.tab)
+ .Select(t => (decimal)t.Attribute(PtOpenXml.TabWidth))
+ .Sum();
+
+ if (runText.Length == 0 && tabLength == 0)
+ return 0;
+
+ int multiplier = 1;
+ if (runText.Length <= 2)
+ multiplier = 100;
+ else if (runText.Length <= 4)
+ multiplier = 50;
+ else if (runText.Length <= 8)
+ multiplier = 25;
+ else if (runText.Length <= 16)
+ multiplier = 12;
+ else if (runText.Length <= 32)
+ multiplier = 6;
+ if (multiplier != 1)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < multiplier; i++)
+ sb.Append(runText);
+ runText = sb.ToString();
+ }
+
+ try
+ {
+ using (Font f = new Font(ff, (float)sz / 2f, fs))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ return sf.Width / multiplier;
+ }
+ }
+ catch (ArgumentException)
+ {
+ try
+ {
+ const FontStyle fs2 = FontStyle.Regular;
+ using (Font f = new Font(ff, (float)sz / 2f, fs2))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ return sf.Width / multiplier;
+ }
+ }
+ catch (ArgumentException)
+ {
+ const FontStyle fs2 = FontStyle.Bold;
+ try
+ {
+ using (var f = new Font(ff, (float)sz / 2f, fs2))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ return sf.Width / multiplier;
+ }
+ }
+ catch (ArgumentException)
+ {
+ // if both regular and bold fail, then get metrics for Times New Roman
+ // use the original FontStyle (in fs)
+ var ff2 = new FontFamily("Times New Roman");
+ using (var f = new Font(ff2, (float)sz / 2f, fs))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ return sf.Width / multiplier;
+ }
+ }
+ }
+ }
+ catch (OverflowException)
+ {
+ // This happened on Azure but interestingly enough not while testing locally.
+ return 0;
+ }
+ }
+
+ // The algorithm for this method comes from the implementer notes in [MS-OI29500].pdf
+ // section 2.1.87
+
+ // The implementer notes are at:
+ // http://msdn.microsoft.com/en-us/library/ee908652.aspx
+
+ public enum FontType
+ {
+ Ascii,
+ HAnsi,
+ EastAsia,
+ CS
+ };
+
+ public class CharStyleAttributes
+ {
+ public string AsciiFont;
+ public string HAnsiFont;
+ public string EastAsiaFont;
+ public string CsFont;
+ public string Hint;
+ public bool Rtl;
+
+ public string LatinLang;
+ public string BidiLang;
+ public string EastAsiaLang;
+
+ public Dictionary<XName, bool?> ToggleProperties;
+ public Dictionary<XName, XElement> Properties;
+
+ public CharStyleAttributes(XElement pPr, XElement rPr)
+ {
+ ToggleProperties = new Dictionary<XName, bool?>();
+ Properties = new Dictionary<XName, XElement>();
+
+ if (rPr == null)
+ return;
+ foreach (XName xn in TogglePropertyNames)
+ {
+ ToggleProperties[xn] = Util.GetBoolProp(rPr, xn);
+ }
+ foreach (XName xn in PropertyNames)
+ {
+ Properties[xn] = GetXmlProperty(rPr, xn);
+ }
+ var rFonts = rPr.Element(W.rFonts);
+ if (rFonts == null)
+ {
+ this.AsciiFont = null;
+ this.HAnsiFont = null;
+ this.EastAsiaFont = null;
+ this.CsFont = null;
+ this.Hint = null;
+ }
+ else
+ {
+ this.AsciiFont = (string)(rFonts.Attribute(W.ascii));
+ this.HAnsiFont = (string)(rFonts.Attribute(W.hAnsi));
+ this.EastAsiaFont = (string)(rFonts.Attribute(W.eastAsia));
+ this.CsFont = (string)(rFonts.Attribute(W.cs));
+ this.Hint = (string)(rFonts.Attribute(W.hint));
+ }
+ XElement csel = this.Properties[W.cs];
+ bool cs = csel != null && (csel.Attribute(W.val) == null || csel.Attribute(W.val).ToBoolean() == true);
+ XElement rtlel = this.Properties[W.rtl];
+ bool rtl = rtlel != null && (rtlel.Attribute(W.val) == null || rtlel.Attribute(W.val).ToBoolean() == true);
+ var bidi = false;
+ if (pPr != null)
+ {
+ XElement bidiel = pPr.Element(W.bidi);
+ bidi = bidiel != null && (bidiel.Attribute(W.val) == null || bidiel.Attribute(W.val).ToBoolean() == true);
+ }
+ Rtl = cs || rtl || bidi;
+ var lang = rPr.Element(W.lang);
+ if (lang != null)
+ {
+ LatinLang = (string)lang.Attribute(W.val);
+ BidiLang = (string)lang.Attribute(W.bidi);
+ EastAsiaLang = (string)lang.Attribute(W.eastAsia);
+ }
+ }
+
+ private static XElement GetXmlProperty(XElement rPr, XName propertyName)
+ {
+ return rPr.Element(propertyName);
+ }
+
+ private static XName[] TogglePropertyNames = new[] {
+ W.b,
+ W.bCs,
+ W.caps,
+ W.emboss,
+ W.i,
+ W.iCs,
+ W.imprint,
+ W.outline,
+ W.shadow,
+ W.smallCaps,
+ W.strike,
+ W.vanish
+ };
+
+ private static XName[] PropertyNames = new[] {
+ W.cs,
+ W.rtl,
+ W.u,
+ W.color,
+ W.highlight,
+ W.shd
+ };
+
+ }
+
+ public static FontType DetermineFontTypeFromCharacter(char ch, CharStyleAttributes csa)
+ {
+ // If the run has the cs element ("[ISO/IEC-29500-1] §17.3.2.7; cs") or the rtl element ("[ISO/IEC-29500-1] §17.3.2.30; rtl"),
+ // then the cs (or cstheme if defined) font is used, regardless of the Unicode character values of the run’s content.
+ if (csa.Rtl)
+ {
+ return FontType.CS;
+ }
+
+ // A large percentage of characters will fall in the following rule.
+
+ // Unicode Block: Basic Latin
+ if (ch >= 0x00 && ch <= 0x7f)
+ {
+ return FontType.Ascii;
+ }
+
+ // If the eastAsia (or eastAsiaTheme if defined) attribute’s value is “Times New Roman” and the ascii (or asciiTheme if defined)
+ // and hAnsi (or hAnsiTheme if defined) attributes are equal, then the ascii (or asciiTheme if defined) font is used.
+ if (csa.EastAsiaFont == "Times New Roman" &&
+ csa.AsciiFont == csa.HAnsiFont)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode BLock: Latin-1 Supplement
+ if (ch >= 0xA0 && ch <= 0xFF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (ch == 0xA1 ||
+ ch == 0xA4 ||
+ ch == 0xA7 ||
+ ch == 0xA8 ||
+ ch == 0xAA ||
+ ch == 0xAD ||
+ ch == 0xAF ||
+ (ch >= 0xB0 && ch <= 0xB4) ||
+ (ch >= 0xB6 && ch <= 0xBA) ||
+ (ch >= 0xBC && ch <= 0xBF) ||
+ ch == 0xD7 ||
+ ch == 0xF7)
+ {
+ return FontType.EastAsia;
+ }
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans")
+ {
+ if (ch == 0xE0 ||
+ ch == 0xE1 ||
+ (ch >= 0xE8 && ch <= 0xEA) ||
+ (ch >= 0xEC && ch <= 0xED) ||
+ (ch >= 0xF2 && ch <= 0xF3) ||
+ (ch >= 0xF9 && ch <= 0xFA) ||
+ ch == 0xFC)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Latin Extended-A
+ if (ch >= 0x0100 && ch <= 0x017F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"
+ /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Latin Extended-B
+ if (ch >= 0x0180 && ch <= 0x024F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"
+ /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: IPA Extensions
+ if (ch >= 0x0250 && ch <= 0x02AF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"
+ /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */)
+ {
+ return FontType.EastAsia;
+ }
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Spacing Modifier Letters
+ if (ch >= 0x02B0 && ch <= 0x02FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Combining Diacritic Marks
+ if (ch >= 0x0300 && ch <= 0x036F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Greek
+ if (ch >= 0x0370 && ch <= 0x03CF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Cyrillic
+ if (ch >= 0x0400 && ch <= 0x04FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Hebrew
+ if (ch >= 0x0590 && ch <= 0x05FF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Arabic
+ if (ch >= 0x0600 && ch <= 0x06FF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Syriac
+ if (ch >= 0x0700 && ch <= 0x074F)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Arabic Supplement
+ if (ch >= 0x0750 && ch <= 0x077F)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Thanna
+ if (ch >= 0x0780 && ch <= 0x07BF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Hangul Jamo
+ if (ch >= 0x1100 && ch <= 0x11FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Latin Extended Additional
+ if (ch >= 0x1E00 && ch <= 0x1EFF)
+ {
+ if (csa.Hint == "eastAsia" &&
+ (csa.EastAsiaLang == "zh-hant" ||
+ csa.EastAsiaLang == "zh-hans"))
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: General Punctuation
+ if (ch >= 0x2000 && ch <= 0x206F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Superscripts and Subscripts
+ if (ch >= 0x2070 && ch <= 0x209F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Currency Symbols
+ if (ch >= 0x20A0 && ch <= 0x20CF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Combining Diacritical Marks for Symbols
+ if (ch >= 0x20D0 && ch <= 0x20FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Letter-like Symbols
+ if (ch >= 0x2100 && ch <= 0x214F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Number Forms
+ if (ch >= 0x2150 && ch <= 0x218F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Arrows
+ if (ch >= 0x2190 && ch <= 0x21FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Mathematical Operators
+ if (ch >= 0x2200 && ch <= 0x22FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Miscellaneous Technical
+ if (ch >= 0x2300 && ch <= 0x23FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Control Pictures
+ if (ch >= 0x2400 && ch <= 0x243F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Optical Character Recognition
+ if (ch >= 0x2440 && ch <= 0x245F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Enclosed Alphanumerics
+ if (ch >= 0x2460 && ch <= 0x24FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Box Drawing
+ if (ch >= 0x2500 && ch <= 0x257F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Block Elements
+ if (ch >= 0x2580 && ch <= 0x259F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Geometric Shapes
+ if (ch >= 0x25A0 && ch <= 0x25FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Miscellaneous Symbols
+ if (ch >= 0x2600 && ch <= 0x26FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Dingbats
+ if (ch >= 0x2700 && ch <= 0x27BF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: CJK Radicals Supplement
+ if (ch >= 0x2E80 && ch <= 0x2EFF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Kangxi Radicals
+ if (ch >= 0x2F00 && ch <= 0x2FDF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Ideographic Description Characters
+ if (ch >= 0x2FF0 && ch <= 0x2FFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Symbols and Punctuation
+ if (ch >= 0x3000 && ch <= 0x303F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Hiragana
+ if (ch >= 0x3040 && ch <= 0x309F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Katakana
+ if (ch >= 0x30A0 && ch <= 0x30FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Bopomofo
+ if (ch >= 0x3100 && ch <= 0x312F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Hangul Compatibility Jamo
+ if (ch >= 0x3130 && ch <= 0x318F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Kanbun
+ if (ch >= 0x3190 && ch <= 0x319F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Enclosed CJK Letters and Months
+ if (ch >= 0x3200 && ch <= 0x32FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Compatibility
+ if (ch >= 0x3300 && ch <= 0x33FF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Unified Ideographs Extension A
+ if (ch >= 0x3400 && ch <= 0x4DBF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: CJK Unified Ideographs
+ if (ch >= 0x4E00 && ch <= 0x9FAF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Yi Syllables
+ if (ch >= 0xA000 && ch <= 0xA48F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Yi Radicals
+ if (ch >= 0xA490 && ch <= 0xA4CF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Hangul Syllables
+ if (ch >= 0xAC00 && ch <= 0xD7AF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: High Surrogates
+ if (ch >= 0xD800 && ch <= 0xDB7F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: High Private Use Surrogates
+ if (ch >= 0xDB80 && ch <= 0xDBFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Low Surrogates
+ if (ch >= 0xDC00 && ch <= 0xDFFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Private Use Area
+ if (ch >= 0xE000 && ch <= 0xF8FF)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: CJK Compatibility Ideographs
+ if (ch >= 0xF900 && ch <= 0xFAFF)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Alphabetic Presentation Forms
+ if (ch >= 0xFB00 && ch <= 0xFB4F)
+ {
+ if (csa.Hint == "eastAsia")
+ {
+ if (ch >= 0xFB00 && ch <= 0xFB1C)
+ return FontType.EastAsia;
+ if (ch >= 0xFB1D && ch <= 0xFB4F)
+ return FontType.Ascii;
+ }
+ return FontType.HAnsi;
+ }
+
+ // Unicode Block: Arabic Presentation Forms-A
+ if (ch >= 0xFB50 && ch <= 0xFDFF)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: CJK Compatibility Forms
+ if (ch >= 0xFE30 && ch <= 0xFE4F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Small Form Variants
+ if (ch >= 0xFE50 && ch <= 0xFE6F)
+ {
+ return FontType.EastAsia;
+ }
+
+ // Unicode Block: Arabic Presentation Forms-B
+ if (ch >= 0xFE70 && ch <= 0xFEFE)
+ {
+ return FontType.Ascii;
+ }
+
+ // Unicode Block: Halfwidth and Fullwidth Forms
+ if (ch >= 0xFF00 && ch <= 0xFFEF)
+ {
+ return FontType.EastAsia;
+ }
+ return FontType.HAnsi;
+ }
+
+ private static readonly HashSet<string> UnknownFonts = new HashSet<string>();
+ private static HashSet<string> _knownFamilies;
+
+ private static HashSet<string> KnownFamilies
+ {
+ get
+ {
+ if (_knownFamilies == null)
+ {
+ _knownFamilies = new HashSet<string>();
+ var families = FontFamily.Families;
+ foreach (var fam in families)
+ _knownFamilies.Add(fam.Name);
+ }
+ return _knownFamilies;
+ }
+ }
+
+ private static HashSet<char> WeakAndNeutralDirectionalCharacters = new HashSet<char>() {
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '+',
+ '-',
+ ':',
+ ',',
+ '.',
+ '|',
+ '\t',
+ '\r',
+ '\n',
+ ' ',
+ '\x00A0', // non breaking space
+
+ '\x00B0', // degree sign
+ '\x066B', // arabic decimal separator
+ '\x066C', // arabic thousands separator
+
+ '\x0627', // arabic pipe
+
+ '\x20A0', // start currency symbols
+ '\x20A1',
+ '\x20A2',
+ '\x20A3',
+ '\x20A4',
+ '\x20A5',
+ '\x20A6',
+ '\x20A7',
+ '\x20A8',
+ '\x20A9',
+ '\x20AA',
+ '\x20AB',
+ '\x20AC',
+ '\x20AD',
+ '\x20AE',
+ '\x20AF',
+ '\x20B0',
+ '\x20B1',
+ '\x20B2',
+ '\x20B3',
+ '\x20B4',
+ '\x20B5',
+ '\x20B6',
+ '\x20B7',
+ '\x20B8',
+ '\x20B9',
+ '\x20BA',
+ '\x20BB',
+ '\x20BC',
+ '\x20BD',
+ '\x20BE',
+ '\x20BF',
+ '\x20C0',
+ '\x20C1',
+ '\x20C2',
+ '\x20C3',
+ '\x20C4',
+ '\x20C5',
+ '\x20C6',
+ '\x20C7',
+ '\x20C8',
+ '\x20C9',
+ '\x20CA',
+ '\x20CB',
+ '\x20CC',
+ '\x20CD',
+ '\x20CE',
+ '\x20CF', // end currency symbols
+
+ '\x0660', // "Arabic" Indic Numeral Forms Iraq and West
+ '\x0661',
+ '\x0662',
+ '\x0663',
+ '\x0664',
+ '\x0665',
+ '\x0666',
+ '\x0667',
+ '\x0668',
+ '\x0669',
+
+ '\x06F0', // "Arabic" Indic Numberal Forms Iran and East
+ '\x06F1',
+ '\x06F2',
+ '\x06F3',
+ '\x06F4',
+ '\x06F5',
+ '\x06F6',
+ '\x06F7',
+ '\x06F8',
+ '\x06F9',
+ };
+
+ private static void AdjustFontAttributes(WordprocessingDocument wDoc, XElement paraOrRun, XElement pPr, XElement rPr)
+ {
+ XDocument themeXDoc = null;
+ if (wDoc.MainDocumentPart.ThemePart != null)
+ themeXDoc = wDoc.MainDocumentPart.ThemePart.GetXDocument();
+
+ XElement fontScheme = null;
+ XElement majorFont = null;
+ XElement minorFont = null;
+ if (themeXDoc != null)
+ {
+ fontScheme = themeXDoc.Root.Element(A.themeElements).Element(A.fontScheme);
+ majorFont = fontScheme.Element(A.majorFont);
+ minorFont = fontScheme.Element(A.minorFont);
+ }
+ var rFonts = rPr.Element(W.rFonts);
+ if (rFonts == null)
+ {
+ return;
+ }
+ var asciiTheme = (string)rFonts.Attribute(W.asciiTheme);
+ var hAnsiTheme = (string)rFonts.Attribute(W.hAnsiTheme);
+ var eastAsiaTheme = (string)rFonts.Attribute(W.eastAsiaTheme);
+ var cstheme = (string)rFonts.Attribute(W.cstheme);
+ string ascii = null;
+ string hAnsi = null;
+ string eastAsia = null;
+ string cs = null;
+
+ XElement minorLatin = null;
+ string minorLatinTypeface = null;
+ XElement majorLatin = null;
+ string majorLatinTypeface = null;
+
+ if (minorFont != null)
+ {
+ minorLatin = minorFont.Element(A.latin);
+ minorLatinTypeface = (string)minorLatin.Attribute("typeface");
+ }
+
+ if (majorFont != null)
+ {
+ majorLatin = majorFont.Element(A.latin);
+ majorLatinTypeface = (string)majorLatin.Attribute("typeface");
+ }
+ if (asciiTheme != null)
+ {
+ if (asciiTheme.StartsWith("minor") && minorLatinTypeface != null)
+ {
+ ascii = minorLatinTypeface;
+ }
+ else if (asciiTheme.StartsWith("major") && majorLatinTypeface != null)
+ {
+ ascii = majorLatinTypeface;
+ }
+ }
+ if (hAnsiTheme != null)
+ {
+ if (hAnsiTheme.StartsWith("minor") && minorLatinTypeface != null)
+ {
+ hAnsi = minorLatinTypeface;
+ }
+ else if (hAnsiTheme.StartsWith("major") && majorLatinTypeface != null)
+ {
+ hAnsi = majorLatinTypeface;
+ }
+ }
+ if (eastAsiaTheme != null)
+ {
+ if (eastAsiaTheme.StartsWith("minor") && minorLatinTypeface != null)
+ {
+ eastAsia = minorLatinTypeface;
+ }
+ else if (eastAsiaTheme.StartsWith("major") && majorLatinTypeface != null)
+ {
+ eastAsia = majorLatinTypeface;
+ }
+ }
+ if (cstheme != null)
+ {
+ if (cstheme.StartsWith("minor") && minorFont != null)
+ {
+ cs = (string)minorFont.Element(A.cs).Attribute("typeface");
+ }
+ else if (cstheme.StartsWith("major") && majorFont != null)
+ {
+ cs = (string)majorFont.Element(A.cs).Attribute("typeface");
+ }
+ }
+
+ if (ascii != null)
+ {
+ rFonts.SetAttributeValue(W.ascii, ascii);
+ }
+ if (hAnsi != null)
+ {
+ rFonts.SetAttributeValue(W.hAnsi, hAnsi);
+ }
+ if (eastAsia != null)
+ {
+ rFonts.SetAttributeValue(W.eastAsia, eastAsia);
+ }
+ if (cs != null)
+ {
+ rFonts.SetAttributeValue(W.cs, cs);
+ }
+
+ var firstTextNode = paraOrRun.Descendants(W.t).FirstOrDefault(t => t.Value.Length > 0);
+ string str = " ";
+
+ // if there is a run with no text in it, then no need to do any of the rest of this method.
+ if (firstTextNode == null && paraOrRun.Name == W.r)
+ return;
+
+ if (firstTextNode != null)
+ str = firstTextNode.Value;
+
+ var csa = new CharStyleAttributes(pPr, rPr);
+
+ // This module determines the font based on just the first character.
+ // Technically, a run can contain characters from different Unicode code blocks, and hence should be rendered with different fonts.
+ // However, Word breaks up runs that use more than one font into multiple runs. Other producers of WordprocessingML may not, so in
+ // that case, this routine may need to be augmented to look at all characters in a run.
+
+ /*
+ old code
+ var fontFamilies = str.select(function (c) {
+ var ft = Pav.DetermineFontTypeFromCharacter(c, csa);
+ switch (ft) {
+ case Pav.FontType.Ascii:
+ return cast(rFonts.attribute(W.ascii));
+ case Pav.FontType.HAnsi:
+ return cast(rFonts.attribute(W.hAnsi));
+ case Pav.FontType.EastAsia:
+ return cast(rFonts.attribute(W.eastAsia));
+ case Pav.FontType.CS:
+ return cast(rFonts.attribute(W.cs));
+ default:
+ return null;
+ }
+ })
+ .where(function (f) { return f != null && f != ""; })
+ .distinct()
+ .select(function (f) { return new Pav.FontFamily(f); })
+ .toArray();
+ */
+
+ var charToExamine = str.FirstOrDefault(c => !WeakAndNeutralDirectionalCharacters.Contains(c));
+ if (charToExamine == '\0')
+ charToExamine = str[0];
+
+ var ft = DetermineFontTypeFromCharacter(charToExamine, csa);
+ string fontType = null;
+ string languageType = null;
+ switch (ft)
+ {
+ case FontType.Ascii:
+ fontType = (string)rFonts.Attribute(W.ascii);
+ languageType = "western";
+ break;
+ case FontType.HAnsi:
+ fontType = (string)rFonts.Attribute(W.hAnsi);
+ languageType = "western";
+ break;
+ case FontType.EastAsia:
+ fontType = (string)rFonts.Attribute(W.eastAsia);
+ languageType = "eastAsia";
+ break;
+ case FontType.CS:
+ fontType = (string)rFonts.Attribute(W.cs);
+ languageType = "bidi";
+ break;
+ }
+
+ if (fontType != null)
+ {
+ if (paraOrRun.Attribute(PtOpenXml.FontName) == null)
+ {
+ XAttribute fta = new XAttribute(PtOpenXml.FontName, fontType.ToString());
+ paraOrRun.Add(fta);
+ }
+ else
+ {
+ paraOrRun.Attribute(PtOpenXml.FontName).Value = fontType.ToString();
+ }
+ }
+ if (languageType != null)
+ {
+ if (paraOrRun.Attribute(PtOpenXml.LanguageType) == null)
+ {
+ XAttribute lta = new XAttribute(PtOpenXml.LanguageType, languageType);
+ paraOrRun.Add(lta);
+ }
+ else
+ {
+ paraOrRun.Attribute(PtOpenXml.LanguageType).Value = languageType;
+ }
+ }
+ }
+
+ private static decimal? GetFontSize(XElement e)
+ {
+ var languageType = (string)e.Attribute(PtOpenXml.LanguageType);
+ if (e.Name == W.p)
+ {
+ return GetFontSize(languageType, e.Elements(W.pPr).Elements(W.rPr).FirstOrDefault());
+ }
+ if (e.Name == W.r)
+ {
+ return GetFontSize(languageType, e.Element(W.rPr));
+ }
+ return null;
+ }
+
+ private static decimal? GetFontSize(string languageType, XElement rPr)
+ {
+ if (rPr == null) return null;
+ return languageType == "bidi"
+ ? (decimal?)rPr.Elements(W.szCs).Attributes(W.val).FirstOrDefault()
+ : (decimal?)rPr.Elements(W.sz).Attributes(W.val).FirstOrDefault();
+ }
+
+ private static int NextRectId = 1025;
+
+ private static int GetNextRectId()
+ {
+ return NextRectId++;
+ }
+
+ private static object GenerateNextExpected(XNode node, HtmlToWmlConverterSettings settings, WordprocessingDocument wDoc,
+ string styleName, NextExpected nextExpected, bool preserveWhiteSpace)
+ {
+ if (nextExpected == NextExpected.Paragraph)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(W.p,
+ GetParagraphProperties(element, styleName, settings),
+ element.Nodes().Select(n => Transform(n, settings, wDoc, NextExpected.Run, preserveWhiteSpace)));
+ }
+ else
+ {
+ XText xTextNode = node as XText;
+ if (xTextNode != null)
+ {
+ string textNodeString = GetDisplayText(xTextNode, preserveWhiteSpace);
+ XElement p;
+ p = new XElement(W.p,
+ GetParagraphProperties(node.Parent, null, settings),
+ new XElement(W.r,
+ GetRunProperties((XText)node, settings),
+ new XElement(W.t,
+ GetXmlSpaceAttribute(textNodeString),
+ textNodeString)));
+ return p;
+ }
+ return null;
+ }
+ }
+ else
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected, preserveWhiteSpace));
+ }
+ else
+ {
+ string textNodeString = GetDisplayText((XText)node, preserveWhiteSpace);
+ XElement rPr = GetRunProperties((XText)node, settings);
+ XElement r = new XElement(W.r,
+ rPr,
+ new XElement(W.t,
+ GetXmlSpaceAttribute(textNodeString),
+ textNodeString));
+ return r;
+ }
+ }
+ }
+
+ private static XElement TransformImageToWml(XElement element, HtmlToWmlConverterSettings settings, WordprocessingDocument wDoc)
+ {
+ string srcAttribute = (string)element.Attribute(XhtmlNoNamespace.src);
+ byte[] ba = null;
+ Bitmap bmp = null;
+
+ if (srcAttribute.StartsWith("data:"))
+ {
+ var semiIndex = srcAttribute.IndexOf(';');
+ var commaIndex = srcAttribute.IndexOf(',', semiIndex);
+ var base64 = srcAttribute.Substring(commaIndex + 1);
+ ba = Convert.FromBase64String(base64);
+ using (MemoryStream ms = new MemoryStream(ba))
+ {
+ bmp = new Bitmap(ms);
+ }
+ }
+ else
+ {
+ try
+ {
+ bmp = new Bitmap(settings.BaseUriForImages + "/" + srcAttribute);
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ catch (NotSupportedException)
+ {
+ return null;
+ }
+ MemoryStream ms = new MemoryStream();
+ bmp.Save(ms, bmp.RawFormat);
+ ba = ms.ToArray();
+ }
+
+ MainDocumentPart mdp = wDoc.MainDocumentPart;
+ string rId = "R" + Guid.NewGuid().ToString().Replace("-", "");
+ ImagePartType ipt = ImagePartType.Png;
+ ImagePart newPart = mdp.AddImagePart(ipt, rId);
+ using (Stream s = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ s.Write(ba, 0, ba.GetUpperBound(0) + 1);
+
+ PictureId pid = wDoc.Annotation<PictureId>();
+ if (pid == null)
+ {
+ pid = new PictureId
+ {
+ Id = 1,
+ };
+ wDoc.AddAnnotation(pid);
+ }
+ int pictureId = pid.Id;
+ ++pid.Id;
+
+ string pictureDescription = "Picture " + pictureId.ToString();
+
+ string floatValue = element.GetProp("float").ToString();
+ if (floatValue == "none")
+ {
+ XElement run = new XElement(W.r,
+ GetRunPropertiesForImage(),
+ new XElement(W.drawing,
+ GetImageAsInline(element, settings, wDoc, bmp, rId, pictureId, pictureDescription)));
+ return run;
+ }
+ if (floatValue == "left" || floatValue == "right")
+ {
+ XElement run = new XElement(W.r,
+ GetRunPropertiesForImage(),
+ new XElement(W.drawing,
+ GetImageAsAnchor(element, settings, wDoc, bmp, rId, floatValue, pictureId, pictureDescription)));
+ return run;
+ }
+ return null;
+ }
+
+ private static XElement GetImageAsInline(XElement element, HtmlToWmlConverterSettings settings, WordprocessingDocument wDoc, Bitmap bmp,
+ string rId, int pictureId, string pictureDescription)
+ {
+ XElement inline = new XElement(WP.inline, // 20.4.2.8
+ new XAttribute(XNamespace.Xmlns + "wp", WP.wp.NamespaceName),
+ new XAttribute(NoNamespace.distT, 0), // distance from top of image to text, in EMUs, no effect if the parent is inline
+ new XAttribute(NoNamespace.distB, 0), // bottom
+ new XAttribute(NoNamespace.distL, 0), // left
+ new XAttribute(NoNamespace.distR, 0), // right
+ GetImageExtent(element, bmp),
+ GetEffectExtent(),
+ GetDocPr(element, pictureId, pictureDescription),
+ GetCNvGraphicFramePr(),
+ GetGraphicForImage(element, rId, bmp, pictureId, pictureDescription));
+ return inline;
+ }
+
+ private static XElement GetImageAsAnchor(XElement element, HtmlToWmlConverterSettings settings, WordprocessingDocument wDoc, Bitmap bmp,
+ string rId, string floatValue, int pictureId, string pictureDescription)
+ {
+ Emu minDistFromEdge = (long)(0.125 * Emu.s_EmusPerInch);
+ long relHeight = 251658240; // z-order
+
+ CssExpression marginTopProp = element.GetProp("margin-top");
+ CssExpression marginLeftProp = element.GetProp("margin-left");
+ CssExpression marginBottomProp = element.GetProp("margin-bottom");
+ CssExpression marginRightProp = element.GetProp("margin-right");
+
+ Emu marginTopInEmus = 0;
+ Emu marginBottomInEmus = 0;
+ Emu marginLeftInEmus = 0;
+ Emu marginRightInEmus = 0;
+
+ if (marginTopProp.IsNotAuto)
+ marginTopInEmus = (Emu)marginTopProp;
+
+ if (marginBottomProp.IsNotAuto)
+ marginBottomInEmus = (Emu)marginBottomProp;
+
+ if (marginLeftProp.IsNotAuto)
+ marginLeftInEmus = (Emu)marginLeftProp;
+
+ if (marginRightProp.IsNotAuto)
+ marginRightInEmus = (Emu)marginRightProp;
+
+ Emu relativeFromColumn = 0;
+ if (floatValue == "left")
+ {
+ relativeFromColumn = marginLeftInEmus;
+ CssExpression parentMarginLeft = element.Parent.GetProp("margin-left");
+ if (parentMarginLeft.IsNotAuto)
+ relativeFromColumn += (long)(Emu)parentMarginLeft;
+ marginRightInEmus = Math.Max(marginRightInEmus, minDistFromEdge);
+ }
+ else if (floatValue == "right")
+ {
+ Emu printWidth = (long)settings.PageWidthEmus - (long)settings.PageMarginLeftEmus - (long)settings.PageMarginRightEmus;
+ SizeEmu sl = GetImageSizeInEmus(element, bmp);
+ relativeFromColumn = printWidth - sl.m_Width;
+ if (marginRightProp.IsNotAuto)
+ relativeFromColumn -= (long)(Emu)marginRightInEmus;
+ CssExpression parentMarginRight = element.Parent.GetProp("margin-right");
+ if (parentMarginRight.IsNotAuto)
+ relativeFromColumn -= (long)(Emu)parentMarginRight;
+ marginLeftInEmus = Math.Max(marginLeftInEmus, minDistFromEdge);
+ }
+
+ Emu relativeFromParagraph = marginTopInEmus;
+ CssExpression parentMarginTop = element.Parent.GetProp("margin-top");
+ if (parentMarginTop.IsNotAuto)
+ relativeFromParagraph += (long)(Emu)parentMarginTop;
+
+ XElement anchor = new XElement(WP.anchor,
+ new XAttribute(XNamespace.Xmlns + "wp", WP.wp.NamespaceName),
+ new XAttribute(NoNamespace.distT, (long)marginTopInEmus), // distance from top of image to text, in EMUs, no effect if the parent is inline
+ new XAttribute(NoNamespace.distB, (long)marginBottomInEmus), // bottom
+ new XAttribute(NoNamespace.distL, (long)marginLeftInEmus), // left
+ new XAttribute(NoNamespace.distR, (long)marginRightInEmus), // right
+ new XAttribute(NoNamespace.simplePos, 0),
+ new XAttribute(NoNamespace.relativeHeight, relHeight),
+ new XAttribute(NoNamespace.behindDoc, 0),
+ new XAttribute(NoNamespace.locked, 0),
+ new XAttribute(NoNamespace.layoutInCell, 1),
+ new XAttribute(NoNamespace.allowOverlap, 1),
+ new XElement(WP.simplePos, new XAttribute(NoNamespace.x, 0), new XAttribute(NoNamespace.y, 0)),
+ new XElement(WP.positionH, new XAttribute(NoNamespace.relativeFrom, "column"),
+ new XElement(WP.posOffset, (long)relativeFromColumn)),
+ new XElement(WP.positionV, new XAttribute(NoNamespace.relativeFrom, "paragraph"),
+ new XElement(WP.posOffset, (long)relativeFromParagraph)),
+ GetImageExtent(element, bmp),
+ GetEffectExtent(),
+ new XElement(WP.wrapSquare, new XAttribute(NoNamespace.wrapText, "bothSides")),
+ GetDocPr(element, pictureId, pictureDescription),
+ GetCNvGraphicFramePr(),
+ GetGraphicForImage(element, rId, bmp, pictureId, pictureDescription),
+ new XElement(WP14.sizeRelH, new XAttribute(NoNamespace.relativeFrom, "page"),
+ new XElement(WP14.pctWidth, 0)),
+ new XElement(WP14.sizeRelV, new XAttribute(NoNamespace.relativeFrom, "page"),
+ new XElement(WP14.pctHeight, 0))
+ );
+ return anchor;
+ }
+#if false
+ <wp:anchor distT="0"
+ distB="0"
+ distL="114300"
+ distR="114300"
+ simplePos="0"
+ relativeHeight="251658240"
+ behindDoc="0"
+ locked="0"
+ layoutInCell="1"
+ allowOverlap="1">
+ <wp:simplePos x="0"
+ y="0"/>
+ <wp:positionH relativeFrom="column">
+ <wp:posOffset>0</wp:posOffset>
+ </wp:positionH>
+ <wp:positionV relativeFrom="paragraph">
+ <wp:posOffset>0</wp:posOffset>
+ </wp:positionV>
+ <wp:extent cx="1713865"
+ cy="1656715"/>
+ <wp:effectExtent l="0"
+ t="0"
+ r="635"
+ b="635"/>
+ <wp:wrapSquare wrapText="bothSides"/>
+ <wp:docPr id="1"
+ name="Picture 1"
+ descr="img.png"/>
+ <wp:cNvGraphicFramePr>
+ <a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
+ noChangeAspect="1"/>
+ </wp:cNvGraphicFramePr>
+ <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
+ <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
+ <pic:nvPicPr>
+ <pic:cNvPr id="0"
+ name="Picture 1"
+ descr="img.png"/>
+ <pic:cNvPicPr>
+ <a:picLocks noChangeAspect="1"
+ noChangeArrowheads="1"/>
+ </pic:cNvPicPr>
+ </pic:nvPicPr>
+ <pic:blipFill>
+ <a:blip r:embed="rId5">
+ <a:extLst>
+ <a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
+ <a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main"
+ val="0"/>
+ </a:ext>
+ </a:extLst>
+ </a:blip>
+ <a:stretch>
+ <a:fillRect/>
+ </a:stretch>
+ </pic:blipFill>
+ <pic:spPr bwMode="auto">
+ <a:xfrm>
+ <a:off x="0"
+ y="0"/>
+ <a:ext cx="1713865"
+ cy="1656715"/>
+ </a:xfrm>
+ <a:prstGeom prst="rect">
+ <a:avLst/>
+ </a:prstGeom>
+ <a:noFill/>
+ <a:ln>
+ <a:noFill/>
+ </a:ln>
+ </pic:spPr>
+ </pic:pic>
+ </a:graphicData>
+ </a:graphic>
+ <wp14:sizeRelH relativeFrom="page">
+ <wp14:pctWidth>0</wp14:pctWidth>
+ </wp14:sizeRelH>
+ <wp14:sizeRelV relativeFrom="page">
+ <wp14:pctHeight>0</wp14:pctHeight>
+ </wp14:sizeRelV>
+ </wp:anchor>
+#endif
+
+ private static XElement GetParagraphPropertiesForImage()
+ {
+ return null;
+ }
+
+ private static XElement GetRunPropertiesForImage()
+ {
+ return new XElement(W.rPr,
+ new XElement(W.noProof));
+ }
+
+ private static SizeEmu GetImageSizeInEmus(XElement img, Bitmap bmp)
+ {
+ double hres = bmp.HorizontalResolution;
+ double vres = bmp.VerticalResolution;
+ Size s = bmp.Size;
+ Emu cx = (long)((double)(s.Width / hres) * (double)Emu.s_EmusPerInch);
+ Emu cy = (long)((double)(s.Height / vres) * (double)Emu.s_EmusPerInch);
+
+ CssExpression width = img.GetProp("width");
+ CssExpression height = img.GetProp("height");
+ if (width.IsNotAuto && height.IsAuto)
+ {
+ Emu widthInEmus = (Emu)width;
+ double percentChange = (float)widthInEmus / (float)cx;
+ cx = widthInEmus;
+ cy = (long)(cy * percentChange);
+ return new SizeEmu(cx, cy);
+ }
+ if (width.IsAuto && height.IsNotAuto)
+ {
+ Emu heightInEmus = (Emu)height;
+ double percentChange = (float)heightInEmus / (float)cy;
+ cy = heightInEmus;
+ cx = (long)(cx * percentChange);
+ return new SizeEmu(cx, cy);
+ }
+ if (width.IsNotAuto && height.IsNotAuto)
+ {
+ return new SizeEmu((Emu)width, (Emu)height);
+ }
+ return new SizeEmu(cx, cy);
+ }
+
+ private static XElement GetImageExtent(XElement img, Bitmap bmp)
+ {
+ SizeEmu szEmu = GetImageSizeInEmus(img, bmp);
+ return new XElement(WP.extent,
+ new XAttribute(NoNamespace.cx, (long)szEmu.m_Width), // in EMUs
+ new XAttribute(NoNamespace.cy, (long)szEmu.m_Height)); // in EMUs
+ }
+
+ private static XElement GetEffectExtent()
+ {
+ return new XElement(WP.effectExtent,
+ new XAttribute(NoNamespace.l, 0),
+ new XAttribute(NoNamespace.t, 0),
+ new XAttribute(NoNamespace.r, 0),
+ new XAttribute(NoNamespace.b, 0));
+ }
+
+ private static XElement GetDocPr(XElement element, int pictureId, string pictureDescription)
+ {
+ return new XElement(WP.docPr,
+ new XAttribute(NoNamespace.id, pictureId),
+ new XAttribute(NoNamespace.name, pictureDescription),
+ new XAttribute(NoNamespace.descr, (string)element.Attribute(NoNamespace.src)));
+ }
+
+ private static XElement GetCNvGraphicFramePr()
+ {
+ return new XElement(WP.cNvGraphicFramePr,
+ new XElement(A.graphicFrameLocks,
+ new XAttribute(XNamespace.Xmlns + "a", A.a.NamespaceName),
+ new XAttribute(NoNamespace.noChangeAspect, 1)));
+ }
+
+ private static XElement GetGraphicForImage(XElement element, string rId, Bitmap bmp, int pictureId, string pictureDescription)
+ {
+ SizeEmu szEmu = GetImageSizeInEmus(element, bmp);
+ XElement graphic = new XElement(A.graphic,
+ new XAttribute(XNamespace.Xmlns + "a", A.a.NamespaceName),
+ new XElement(A.graphicData,
+ new XAttribute(NoNamespace.uri, Pic.pic.NamespaceName),
+ new XElement(Pic._pic,
+ new XAttribute(XNamespace.Xmlns + "pic", Pic.pic.NamespaceName),
+ new XElement(Pic.nvPicPr,
+ new XElement(Pic.cNvPr,
+ new XAttribute(NoNamespace.id, pictureId),
+ new XAttribute(NoNamespace.name, pictureDescription),
+ new XAttribute(NoNamespace.descr, (string)element.Attribute(NoNamespace.src))),
+ new XElement(Pic.cNvPicPr,
+ new XElement(A.picLocks,
+ new XAttribute(NoNamespace.noChangeAspect, 1),
+ new XAttribute(NoNamespace.noChangeArrowheads, 1)))),
+ new XElement(Pic.blipFill,
+ new XElement(A.blip,
+ new XAttribute(R.embed, rId),
+ new XElement(A.extLst,
+ new XElement(A.ext,
+ new XAttribute(NoNamespace.uri, "{28A0092B-C50C-407E-A947-70E740481C1C}"),
+ new XElement(A14.useLocalDpi,
+ new XAttribute(NoNamespace.val, "0"))))),
+ new XElement(A.stretch,
+ new XElement(A.fillRect))),
+ new XElement(Pic.spPr,
+ new XAttribute(NoNamespace.bwMode, "auto"),
+ new XElement(A.xfrm,
+ new XElement(A.off, new XAttribute(NoNamespace.x, 0), new XAttribute(NoNamespace.y, 0)),
+ new XElement(A.ext, new XAttribute(NoNamespace.cx, (long)szEmu.m_Width), new XAttribute(NoNamespace.cy, (long)szEmu.m_Height))),
+ new XElement(A.prstGeom, new XAttribute(NoNamespace.prst, "rect"),
+ new XElement(A.avLst)),
+ new XElement(A.noFill),
+ new XElement(A.ln,
+ new XElement(A.noFill))))));
+ return graphic;
+ }
+
+#if false
+ <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
+ <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
+ <pic:nvPicPr>
+ <pic:cNvPr id="0" name="Picture 1" descr="img.png"/>
+ <pic:cNvPicPr>
+ <a:picLocks noChangeAspect="1" noChangeArrowheads="1"/>
+ </pic:cNvPicPr>
+ </pic:nvPicPr>
+ <pic:blipFill>
+ <a:blip r:link="rId5">
+ <a:extLst>
+ <a:ext uri="{28A0092B-C50C-407E-A947-70E740481C1C}">
+ <a14:useLocalDpi xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" val="0"/>
+ </a:ext>
+ </a:extLst>
+ </a:blip>
+ <a:srcRect/>
+ <a:stretch>
+ <a:fillRect/>
+ </a:stretch>
+ </pic:blipFill>
+ <pic:spPr bwMode="auto">
+ <a:xfrm>
+ <a:off x="0" y="0"/>
+ <a:ext cx="1781175" cy="1781175"/>
+ </a:xfrm>
+ <a:prstGeom prst="rect">
+ <a:avLst/>
+ </a:prstGeom>
+ <a:noFill/>
+ <a:ln>
+ <a:noFill/>
+ </a:ln>
+ </pic:spPr>
+ </pic:pic>
+ </a:graphicData>
+ </a:graphic>
+#endif
+ private static XElement GetParagraphProperties(XElement blockLevelElement, string styleName, HtmlToWmlConverterSettings settings)
+ {
+ XElement paragraphMarkRunProperties = GetRunProperties(blockLevelElement, settings);
+ XElement backgroundProperty = GetBackgroundProperty(blockLevelElement);
+ XElement[] spacingProperty = GetSpacingProperties(blockLevelElement, settings); // spacing, ind, contextualSpacing
+ XElement jc = GetJustification(blockLevelElement, settings);
+ XElement pStyle = styleName != null ? new XElement(W.pStyle, new XAttribute(W.val, styleName)) : null;
+ XElement numPr = GetNumberingProperties(blockLevelElement, settings);
+ XElement pBdr = GetBlockContentBorders(blockLevelElement, W.pBdr, true);
+
+ XElement bidi = null;
+ string direction = GetDirection(blockLevelElement);
+ if (direction == "rtl")
+ bidi = new XElement(W.bidi);
+
+ XElement pPr = new XElement(W.pPr,
+ pStyle,
+ numPr,
+ pBdr,
+ backgroundProperty,
+ bidi,
+ spacingProperty,
+ jc,
+ paragraphMarkRunProperties
+ );
+ return pPr;
+ }
+
+ // vertical-align doesn't really work in the Word rendering - puts space above, but not below. There really are no
+ // options in WordprocessingML to specify vertical alignment. I think that the only possible way that this could be
+ // implemented would be to specifically calculate the space before and space after. I'm not completely sure that
+ // this could be possible. I am pretty sure that this is not worth the effort.
+
+ // Returns the spacing, ind, and contextualSpacing elements
+ private static XElement[] GetSpacingProperties(XElement paragraph, HtmlToWmlConverterSettings settings)
+ {
+ CssExpression marginLeftProperty = paragraph.GetProp("margin-left");
+ CssExpression marginRightProperty = paragraph.GetProp("margin-right");
+ CssExpression marginTopProperty = paragraph.GetProp("margin-top");
+ CssExpression marginBottomProperty = paragraph.GetProp("margin-bottom");
+ CssExpression lineHeightProperty = paragraph.GetProp("line-height");
+ CssExpression leftPaddingProperty = paragraph.GetProp("padding-left");
+ CssExpression rightPaddingProperty = paragraph.GetProp("padding-right");
+
+ /*****************************************************************************************/
+ // leftIndent, rightIndent, firstLine
+
+ Twip leftIndent = 0;
+ Twip rightIndent = 0;
+ Twip firstLine = 0;
+
+#if false
+ // this code is here for some reason. What is it?
+ double leftBorderSize = GetBorderSize(paragraph, "left"); // in 1/8 point
+ double rightBorderSize = GetBorderSize(paragraph, "right"); // in 1/8 point
+ leftIndent += (long)((leftBorderSize / 8d) * 20d);
+ rightIndent += (long)((rightBorderSize / 8d) * 20d);
+#endif
+
+ if (leftPaddingProperty != null)
+ leftIndent += (Twip)leftPaddingProperty;
+ if (rightPaddingProperty != null)
+ rightIndent += (Twip)rightPaddingProperty;
+
+ if (paragraph.Name == XhtmlNoNamespace.li)
+ {
+ leftIndent += 180;
+ rightIndent += 180;
+ }
+ XElement listElement = null;
+ NumberedItemAnnotation numberedItemAnnotation = null;
+ listElement = paragraph.Ancestors().FirstOrDefault(a => a.Name == XhtmlNoNamespace.ol || a.Name == XhtmlNoNamespace.ul);
+ if (listElement != null)
+ {
+ numberedItemAnnotation = listElement.Annotation<NumberedItemAnnotation>();
+ leftIndent += 600 * (numberedItemAnnotation.ilvl + 1);
+ }
+
+ int blockQuoteCount = paragraph.Ancestors(XhtmlNoNamespace.blockquote).Count();
+ leftIndent += blockQuoteCount * 720;
+ if (blockQuoteCount == 0)
+ {
+ if (marginLeftProperty != null && marginLeftProperty.IsNotAuto && marginLeftProperty.IsNotNormal)
+ leftIndent += (Twip)marginLeftProperty;
+ if (marginRightProperty != null && marginRightProperty.IsNotAuto && marginRightProperty.IsNotNormal)
+ rightIndent += (Twip)marginRightProperty;
+ }
+ CssExpression textIndentProperty = paragraph.GetProp("text-indent");
+ if (textIndentProperty != null)
+ {
+ Twip twips = (Twip)textIndentProperty;
+ firstLine = twips;
+ }
+
+ XElement ind = null;
+ if (leftIndent > 0 || rightIndent > 0 || firstLine != 0)
+ {
+ if (firstLine < 0)
+ ind = new XElement(W.ind,
+ leftIndent != 0 ? new XAttribute(W.left, (long)leftIndent) : null,
+ rightIndent != 0 ? new XAttribute(W.right, (long)rightIndent) : null,
+ firstLine != 0 ? new XAttribute(W.hanging, -(long)firstLine) : null);
+ else
+ ind = new XElement(W.ind,
+ leftIndent != 0 ? new XAttribute(W.left, (long)leftIndent) : null,
+ rightIndent != 0 ? new XAttribute(W.right, (long)rightIndent) : null,
+ firstLine != 0 ? new XAttribute(W.firstLine, (long)firstLine) : null);
+ }
+
+ /*****************************************************************************************/
+ // spacing
+
+ long line = 240;
+ string lineRule = "auto";
+ string beforeAutospacing = null;
+ string afterAutospacing = null;
+ long? before = null;
+ long? after = null;
+
+ if (paragraph.Name == XhtmlNoNamespace.td || paragraph.Name == XhtmlNoNamespace.th || paragraph.Name == XhtmlNoNamespace.caption)
+ {
+ line = (long)settings.DefaultSpacingElementForParagraphsInTables.Attribute(W.line);
+ lineRule = (string)settings.DefaultSpacingElementForParagraphsInTables.Attribute(W.lineRule);
+ before = (long?)settings.DefaultSpacingElementForParagraphsInTables.Attribute(W.before);
+ beforeAutospacing = (string)settings.DefaultSpacingElementForParagraphsInTables.Attribute(W.beforeAutospacing);
+ after = (long?)settings.DefaultSpacingElementForParagraphsInTables.Attribute(W.after);
+ afterAutospacing = (string)settings.DefaultSpacingElementForParagraphsInTables.Attribute(W.afterAutospacing);
+ }
+
+ // todo should check based on display property
+ bool numItem = paragraph.Name == XhtmlNoNamespace.li;
+
+ if (numItem && marginTopProperty.IsAuto)
+ beforeAutospacing = "1";
+ if (numItem && marginBottomProperty.IsAuto)
+ afterAutospacing = "1";
+ if (marginTopProperty != null && marginTopProperty.IsNotAuto)
+ {
+ before = (long)(Twip)marginTopProperty;
+ beforeAutospacing = "0";
+ }
+ if (marginBottomProperty != null && marginBottomProperty.IsNotAuto)
+ {
+ after = (long)(Twip)marginBottomProperty;
+ afterAutospacing = "0";
+ }
+ if (lineHeightProperty != null && lineHeightProperty.IsNotAuto && lineHeightProperty.IsNotNormal)
+ {
+ // line is in twips if lineRule == "atLeast"
+ line = (long)(Twip)lineHeightProperty;
+ lineRule = "atLeast";
+ }
+
+ XElement spacing = new XElement(W.spacing,
+ before != null ? new XAttribute(W.before, before) : null,
+ beforeAutospacing != null ? new XAttribute(W.beforeAutospacing, beforeAutospacing) : null,
+ after != null ? new XAttribute(W.after, after) : null,
+ afterAutospacing != null ? new XAttribute(W.afterAutospacing, afterAutospacing) : null,
+ new XAttribute(W.line, line),
+ new XAttribute(W.lineRule, lineRule));
+
+ /*****************************************************************************************/
+ // contextualSpacing
+
+ XElement contextualSpacing = null;
+ if (paragraph.Name == XhtmlNoNamespace.li)
+ {
+ NumberedItemAnnotation thisNumberedItemAnnotation = null;
+ XElement listElement2 = paragraph.Ancestors().FirstOrDefault(a => a.Name == XhtmlNoNamespace.ol || a.Name == XhtmlNoNamespace.ul);
+ if (listElement2 != null)
+ {
+ thisNumberedItemAnnotation = listElement2.Annotation<NumberedItemAnnotation>();
+ XElement next = paragraph.ElementsAfterSelf().FirstOrDefault();
+ if (next != null && next.Name == XhtmlNoNamespace.li)
+ {
+ XElement nextListElement = next.Ancestors().FirstOrDefault(a => a.Name == XhtmlNoNamespace.ol || a.Name == XhtmlNoNamespace.ul);
+ NumberedItemAnnotation nextNumberedItemAnnotation = nextListElement.Annotation<NumberedItemAnnotation>();
+ if (nextNumberedItemAnnotation != null && thisNumberedItemAnnotation.numId == nextNumberedItemAnnotation.numId)
+ contextualSpacing = new XElement(W.contextualSpacing);
+ }
+ }
+ }
+
+ return new XElement[] { spacing, ind, contextualSpacing };
+ }
+
+ private static XElement GetRunProperties(XText textNode, HtmlToWmlConverterSettings settings)
+ {
+ XElement parent = textNode.Parent;
+ XElement rPr = GetRunProperties(parent, settings);
+ return rPr;
+ }
+
+ private static XElement GetRunProperties(XElement element, HtmlToWmlConverterSettings settings)
+ {
+ CssExpression colorProperty = element.GetProp("color");
+ CssExpression fontFamilyProperty = element.GetProp("font-family");
+ CssExpression fontSizeProperty = element.GetProp("font-size");
+ CssExpression textDecorationProperty = element.GetProp("text-decoration");
+ CssExpression fontStyleProperty = element.GetProp("font-style");
+ CssExpression fontWeightProperty = element.GetProp("font-weight");
+ CssExpression backgroundColorProperty = element.GetProp("background-color");
+ CssExpression letterSpacingProperty = element.GetProp("letter-spacing");
+ CssExpression directionProp = element.GetProp("direction");
+
+ string colorPropertyString = colorProperty.ToString();
+ string fontFamilyString = GetUsedFontFromFontFamilyProperty(fontFamilyProperty);
+ TPoint? fontSizeTPoint = GetUsedSizeFromFontSizeProperty(fontSizeProperty);
+ string textDecorationString = textDecorationProperty.ToString();
+ string fontStyleString = fontStyleProperty.ToString();
+ string fontWeightString = fontWeightProperty.ToString().ToLower();
+ string backgroundColorString = backgroundColorProperty.ToString().ToLower();
+ string letterSpacingString = letterSpacingProperty.ToString().ToLower();
+ string directionString = directionProp.ToString().ToLower();
+
+ bool subAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.sub).Any();
+ bool supAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.sup).Any();
+ bool bAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.b).Any();
+ bool iAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.i).Any();
+ bool strongAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.strong).Any();
+ bool emAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.em).Any();
+ bool uAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.u).Any();
+ bool sAncestor = element.AncestorsAndSelf(XhtmlNoNamespace.s).Any();
+
+ XAttribute dirAttribute = element.Attribute(XhtmlNoNamespace.dir);
+ string dirAttributeString = "";
+ if (dirAttribute != null)
+ dirAttributeString = dirAttribute.Value.ToLower();
+
+ XElement shd = null;
+ if (backgroundColorString != "transparent")
+ shd = new XElement(W.shd, new XAttribute(W.val, "clear"),
+ new XAttribute(W.color, "auto"),
+ new XAttribute(W.fill, backgroundColorString));
+
+ XElement subSuper = null;
+ if (subAncestor)
+ subSuper = new XElement(W.vertAlign, new XAttribute(W.val, "subscript"));
+ else
+ if (supAncestor)
+ subSuper = new XElement(W.vertAlign, new XAttribute(W.val, "superscript"));
+
+ XElement rFonts = null;
+ if (fontFamilyString != null)
+ {
+ rFonts = new XElement(W.rFonts,
+ fontFamilyString != settings.MinorLatinFont ? new XAttribute(W.ascii, fontFamilyString) : null,
+ fontFamilyString != settings.MajorLatinFont ? new XAttribute(W.hAnsi, fontFamilyString) : null,
+ new XAttribute(W.cs, fontFamilyString));
+ }
+
+ // todo I think this puts a color on every element.
+ XElement color = colorPropertyString != null ?
+ new XElement(W.color, new XAttribute(W.val, colorPropertyString)) : null;
+
+ XElement sz = null;
+ XElement szCs = null;
+ if (fontSizeTPoint != null)
+ {
+ sz = new XElement(W.sz, new XAttribute(W.val, (int)((double)fontSizeTPoint * 2)));
+ szCs = new XElement(W.szCs, new XAttribute(W.val, (int)((double)fontSizeTPoint * 2)));
+ }
+
+ XElement strike = null;
+ if (textDecorationString == "line-through" || sAncestor)
+ strike = new XElement(W.strike);
+
+ XElement bold = null;
+ XElement boldCs = null;
+ if (bAncestor || strongAncestor || fontWeightString == "bold" || fontWeightString == "bolder" || fontWeightString == "600" || fontWeightString == "700" || fontWeightString == "800" || fontWeightString == "900")
+ {
+ bold = new XElement(W.b);
+ boldCs = new XElement(W.bCs);
+ }
+
+ XElement italic = null;
+ XElement italicCs = null;
+ if (iAncestor || emAncestor || fontStyleString == "italic")
+ {
+ italic = new XElement(W.i);
+ italicCs = new XElement(W.iCs);
+ }
+
+ XElement underline = null;
+ if (uAncestor || textDecorationString == "underline")
+ underline = new XElement(W.u, new XAttribute(W.val, "single"));
+
+ XElement rStyle = null;
+ if (element.Name == XhtmlNoNamespace.a)
+ rStyle = new XElement(W.rStyle,
+ new XAttribute(W.val, "Hyperlink"));
+
+ XElement spacing = null;
+ if (letterSpacingProperty.IsNotNormal)
+ spacing = new XElement(W.spacing,
+ new XAttribute(W.val, (long)(Twip)letterSpacingProperty));
+
+ XElement rtl = null;
+ if (dirAttributeString == "rtl" || directionString == "rtl")
+ rtl = new XElement(W.rtl);
+
+ XElement runProps = new XElement(W.rPr,
+ rStyle,
+ rFonts,
+ bold,
+ boldCs,
+ italic,
+ italicCs,
+ strike,
+ color,
+ spacing,
+ sz,
+ szCs,
+ underline,
+ shd,
+ subSuper,
+ rtl);
+
+ if (runProps.Elements().Any())
+ return runProps;
+
+ return null;
+ }
+
+ // todo can make this faster
+ // todo this is not right - needs to be rationalized for all characters in an entire paragraph.
+ // if there is text like <p>abc <em> def </em> ghi</p> then there needs to be just one space between abc and def, and between
+ // def and ghi.
+ private static string GetDisplayText(XText node, bool preserveWhiteSpace)
+ {
+ string textTransform = node.Parent.GetProp("text-transform").ToString();
+ bool isFirst = node.Parent.Name == XhtmlNoNamespace.p && node == node.Parent.FirstNode;
+ bool isLast = node.Parent.Name == XhtmlNoNamespace.p && node == node.Parent.LastNode;
+
+ IEnumerable<IGrouping<bool, char>> groupedCharacters = null;
+ if (preserveWhiteSpace)
+ groupedCharacters = node.Value.GroupAdjacent(c => c == '\r' || c == '\n');
+ else
+ groupedCharacters = node.Value.GroupAdjacent(c => c == ' ' || c == '\r' || c == '\n');
+
+ string newString = groupedCharacters.Select(g =>
+ {
+ if (g.Key == true)
+ return " ";
+ string x = g.Select(c => c.ToString()).StringConcatenate();
+ return x;
+ })
+ .StringConcatenate();
+ if (!preserveWhiteSpace)
+ {
+ if (isFirst)
+ newString = newString.TrimStart();
+ if (isLast)
+ newString = newString.TrimEnd();
+ }
+ if (textTransform == "uppercase")
+ newString = newString.ToUpper();
+ else if (textTransform == "lowercase")
+ newString = newString.ToLower();
+ else if (textTransform == "capitalize")
+ newString = newString.Substring(0, 1).ToUpper() + newString.Substring(1).ToLower();
+ return newString;
+ }
+
+ private static XElement GetNumberingProperties(XElement paragraph, HtmlToWmlConverterSettings settings)
+ {
+ // Numbering properties ******************************************************
+ NumberedItemAnnotation numberedItemAnnotation = null;
+ XElement listElement = paragraph.Ancestors().FirstOrDefault(a => a.Name == XhtmlNoNamespace.ol || a.Name == XhtmlNoNamespace.ul);
+ if (listElement != null)
+ {
+ numberedItemAnnotation = listElement.Annotation<NumberedItemAnnotation>();
+ }
+ XElement numPr = null;
+ if (paragraph.Name == XhtmlNoNamespace.li)
+ numPr = new XElement(W.numPr,
+ new XElement(W.ilvl, new XAttribute(W.val, numberedItemAnnotation.ilvl)),
+ new XElement(W.numId, new XAttribute(W.val, numberedItemAnnotation.numId)));
+ return numPr;
+ }
+
+ private static XElement GetJustification(XElement blockLevelElement, HtmlToWmlConverterSettings settings)
+ {
+ // Justify ******************************************************
+ CssExpression textAlignProperty = blockLevelElement.GetProp("text-align");
+ string textAlign;
+ if (blockLevelElement.Name == XhtmlNoNamespace.caption || blockLevelElement.Name == XhtmlNoNamespace.th)
+ textAlign = "center";
+ else
+ textAlign = "left";
+ if (textAlignProperty != null)
+ textAlign = textAlignProperty.ToString();
+ string jc = null;
+ if (textAlign == "center")
+ jc = "center";
+ else
+ {
+ if (textAlign == "right")
+ jc = "right";
+ else
+ {
+ if (textAlign == "justify")
+ jc = "both";
+ }
+ }
+ string direction = GetDirection(blockLevelElement);
+ if (direction == "rtl")
+ {
+ if (jc == "left")
+ jc = "right";
+ else if (jc == "right")
+ jc = "left";
+ }
+ XElement jcElement = null;
+ if (jc != null)
+ jcElement = new XElement(W.jc, new XAttribute(W.val, jc));
+ return jcElement;
+ }
+
+ private class HeadingInfo
+ {
+ public XName Name;
+ public string StyleName;
+ };
+
+ private static HeadingInfo[] HeadingTagMap = new[]
+ {
+ new HeadingInfo { Name = XhtmlNoNamespace.h1, StyleName = "Heading1" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h2, StyleName = "Heading2" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h3, StyleName = "Heading3" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h4, StyleName = "Heading4" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h5, StyleName = "Heading5" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h6, StyleName = "Heading6" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h7, StyleName = "Heading7" },
+ new HeadingInfo { Name = XhtmlNoNamespace.h8, StyleName = "Heading8" },
+ };
+
+ private static string GetDirection(XElement element)
+ {
+ string retValue = "ltr";
+ string dirString = (string)element.Attribute(XhtmlNoNamespace.dir);
+ if (dirString != null && dirString.ToLower() == "rtl")
+ retValue = "rtl";
+ CssExpression directionProp = element.GetProp("direction");
+ if (directionProp != null)
+ {
+ string directionValue = directionProp.ToString();
+ if (directionValue.ToLower() == "rtl")
+ retValue = "rtl";
+ }
+ return retValue;
+ }
+
+ private static XElement GetTableProperties(XElement element)
+ {
+
+ XElement bidiVisual = null;
+ string direction = GetDirection(element);
+ if (direction == "rtl")
+ bidiVisual = new XElement(W.bidiVisual);
+
+ XElement tblPr = new XElement(W.tblPr,
+ bidiVisual,
+ GetTableWidth(element),
+ GetTableCellSpacing(element),
+ GetBlockContentBorders(element, W.tblBorders, false),
+ GetTableShading(element),
+ GetTableCellMargins(element),
+ GetTableLook(element));
+ return tblPr;
+ }
+
+ private static XElement GetTableShading(XElement element)
+ {
+ // todo this is not done.
+ // needs to work for W.tbl and W.tc
+ //XElement shd = new XElement(W.shd,
+ // new XAttribute(W.val, "clear"),
+ // new XAttribute(W.color, "auto"),
+ // new XAttribute(W.fill, "ffffff"));
+ //return shd;
+ return null;
+ }
+
+ private static XElement GetTableWidth(XElement element)
+ {
+ CssExpression width = element.GetProp("width");
+ if (width.IsAuto)
+ {
+ return new XElement(W.tblW,
+ new XAttribute(W._w, "0"),
+ new XAttribute(W.type, "auto"));
+ }
+ XElement widthElement = new XElement(W.tblW,
+ new XAttribute(W._w, (long)(Twip)width),
+ new XAttribute(W.type, "dxa"));
+ return widthElement;
+ }
+
+ private static XElement GetCellWidth(XElement element)
+ {
+ CssExpression width = element.GetProp("width");
+ if (width.IsAuto)
+ {
+ return new XElement(W.tcW,
+ new XAttribute(W._w, "0"),
+ new XAttribute(W.type, "auto"));
+ }
+ XElement widthElement = new XElement(W.tcW,
+ new XAttribute(W._w, (long)(Twip)width),
+ new XAttribute(W.type, "dxa"));
+ return widthElement;
+ }
+
+ private static XElement GetBlockContentBorders(XElement element, XName borderXName, bool forParagraph)
+ {
+ if ((element.Name == XhtmlNoNamespace.td || element.Name == XhtmlNoNamespace.th || element.Name == XhtmlNoNamespace.caption) && forParagraph)
+ return null;
+ XElement borders = new XElement(borderXName,
+ new XElement(W.top, GetBorderAttributes(element, "top")),
+ new XElement(W.left, GetBorderAttributes(element, "left")),
+ new XElement(W.bottom, GetBorderAttributes(element, "bottom")),
+ new XElement(W.right, GetBorderAttributes(element, "right")));
+ if (borders.Elements().Attributes(W.val).Where(v => (string)v == "none").Count() == 4)
+ return null;
+ return borders;
+ }
+
+ private static Dictionary<string, string> BorderStyleMap = new Dictionary<string, string>()
+ {
+ { "none", "none" },
+ { "hidden", "none" },
+ { "dotted", "dotted" },
+ { "dashed", "dashed" },
+ { "solid", "single" },
+ { "double", "double" },
+ { "groove", "inset" },
+ { "ridge", "outset" },
+ { "inset", "inset" },
+ { "outset", "outset" },
+ };
+
+ private static List<XAttribute> GetBorderAttributes(XElement element, string whichBorder)
+ {
+ //if (whichBorder == "right")
+ // Console.WriteLine(1);
+ CssExpression styleProp = element.GetProp(string.Format("border-{0}-style", whichBorder));
+ CssExpression colorProp = element.GetProp(string.Format("border-{0}-color", whichBorder));
+ CssExpression paddingProp = element.GetProp(string.Format("padding-{0}", whichBorder));
+ CssExpression marginProp = element.GetProp(string.Format("margin-{0}", whichBorder));
+
+ // The space attribute is equivalent to the margin properties of CSS
+ // the ind element of the parent is more or less equivalent to the padding properties of CSS, except that ind takes space
+ // AWAY from the space attribute, therefore ind needs to be increased by the amount of padding.
+
+ // if there is no border, and yet there is padding, then need to create a thin border so that word will display the background
+ // color of the paragraph properly (including padding).
+
+ XAttribute val = null;
+ XAttribute sz = null;
+ XAttribute space = null;
+ XAttribute color = null;
+
+ if (styleProp != null)
+ {
+ if (BorderStyleMap.ContainsKey(styleProp.ToString()))
+ val = new XAttribute(W.val, BorderStyleMap[styleProp.ToString()]);
+ else
+ val = new XAttribute(W.val, "none");
+ }
+
+ double borderSizeInTwips = GetBorderSize(element, whichBorder);
+
+ double borderSizeInOneEighthPoint = borderSizeInTwips / 20 * 8;
+ sz = new XAttribute(W.sz, (int)borderSizeInOneEighthPoint);
+
+ if (element.Name == XhtmlNoNamespace.td || element.Name == XhtmlNoNamespace.th)
+ {
+ space = new XAttribute(W.space, "0");
+#if false
+ // 2012-05-14 todo alternative algorithm for margin for cells
+ if (marginProp != null)
+ {
+ // space is specified in points, not twips
+ TPoint points = 0;
+ if (marginProp.IsNotAuto)
+ points = (TPoint)marginProp;
+ space = new XAttribute(W.space, Math.Min(31, (double)points));
+ }
+#endif
+ }
+ else
+ {
+ space = new XAttribute(W.space, "0");
+ if (paddingProp != null)
+ {
+ // space is specified in points, not twips
+ TPoint points = (TPoint)paddingProp;
+ space = new XAttribute(W.space, (int)(Math.Min(31, (double)points)));
+ }
+ }
+
+ if (colorProp != null)
+ color = new XAttribute(W.color, colorProp.ToString());
+ // no default yet
+
+ if ((string)val == "none" && (double)space != 0d)
+ {
+ val.Value = "single";
+ sz.Value = "0";
+ //color.Value = "FF0000";
+ }
+
+ // sz is in 1/8 of a point
+ // space is in 1/20 of a point
+
+ List<XAttribute> attList = new List<XAttribute>()
+ {
+ val,
+ sz,
+ space,
+ color,
+ };
+ return attList;
+ }
+
+ private static Twip GetBorderSize(XElement element, string whichBorder)
+ {
+ CssExpression widthProp = element.GetProp(string.Format("border-{0}-width", whichBorder));
+
+ if (widthProp != null && widthProp.Terms.Count() == 1)
+ {
+ CssTerm term = widthProp.Terms.First();
+ Twip twips = (Twip)widthProp;
+ return twips;
+ }
+ return 12;
+ }
+
+ private static XElement GetTableLook(XElement element)
+ {
+ XElement tblLook = XElement.Parse(
+ //@"<w:tblLook w:val='0600'
+ // w:firstRow='0'
+ // w:lastRow='0'
+ // w:firstColumn='0'
+ // w:lastColumn='0'
+ // w:noHBand='1'
+ // w:noVBand='1'
+ // xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'/>"
+
+@"<w:tblLook w:val='0600' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'/>"
+
+);
+ tblLook.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
+ return tblLook;
+ }
+
+ private static XElement GetTableGrid(XElement element, HtmlToWmlConverterSettings settings)
+ {
+ Twip? pageWidthInTwips = (int?)settings.SectPr.Elements(W.pgSz).Attributes(W._w).FirstOrDefault();
+ Twip? marginLeft = (int?)settings.SectPr.Elements(W.pgMar).Attributes(W.left).FirstOrDefault();
+ Twip? marginRight = (int?)settings.SectPr.Elements(W.pgMar).Attributes(W.right).FirstOrDefault();
+ Twip printable = (long)pageWidthInTwips - (long)marginLeft - (long)marginRight;
+ XElement[][] tableArray = GetTableArray(element);
+ int numberColumns = tableArray[0].Length;
+ CssExpression[] columnWidths = new CssExpression[numberColumns];
+ for (int c = 0; c < numberColumns; c++)
+ {
+ CssExpression columnWidth = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "auto" } } };
+ for (int r = 0; r < tableArray.Length; ++r)
+ {
+ if (tableArray[r][c] != null)
+ {
+ XElement cell = tableArray[r][c];
+ CssExpression width = cell.GetProp("width");
+ XAttribute colSpan = cell.Attribute(XhtmlNoNamespace.colspan);
+ if (colSpan == null && columnWidth.ToString() == "auto" && width.ToString() != "auto")
+ {
+ columnWidth = width;
+ break;
+ }
+ }
+ }
+ columnWidths[c] = columnWidth;
+ }
+
+ XElement tblGrid = new XElement(W.tblGrid,
+ columnWidths.Select(cw => new XElement(W.gridCol,
+ new XAttribute(W._w, (long)GetTwipWidth(cw, (int)printable)))));
+ return tblGrid;
+ }
+
+ private static Twip GetTwipWidth(CssExpression columnWidth, int printable)
+ {
+ Twip defaultTwipWidth = 1440;
+ if (columnWidth.Terms.Count() == 1)
+ {
+ CssTerm term = columnWidth.Terms.First();
+ if (term.Unit == CssUnit.PT)
+ {
+ Double ptValue;
+ if (Double.TryParse(term.Value, out ptValue))
+ {
+ Twip twips = (long)(ptValue * 20);
+ return twips;
+ }
+ return defaultTwipWidth;
+ }
+ }
+ return defaultTwipWidth;
+ }
+
+ private static XElement[][] GetTableArray(XElement table)
+ {
+ List<XElement> rowList = table.DescendantsTrimmed(XhtmlNoNamespace.table).Where(e => e.Name == XhtmlNoNamespace.tr).ToList();
+ int numberColumns = rowList.Select(r => r.Elements().Where(e => e.Name == XhtmlNoNamespace.td || e.Name == XhtmlNoNamespace.th).Count()).Max();
+ XElement[][] tableArray = new XElement[rowList.Count()][];
+ int rowNumber = 0;
+ foreach (var row in rowList)
+ {
+ tableArray[rowNumber] = new XElement[numberColumns];
+ int columnNumber = 0;
+ foreach (var cell in row.Elements(XhtmlNoNamespace.td))
+ {
+ tableArray[rowNumber][columnNumber] = cell;
+ columnNumber++;
+ }
+ rowNumber++;
+ }
+ return tableArray;
+ }
+
+ private static XElement GetCellPropertiesForCaption(XElement element)
+ {
+ XElement gridSpan = new XElement(W.gridSpan,
+ new XAttribute(W.val, 3));
+
+ XElement tcBorders = GetBlockContentBorders(element, W.tcBorders, false);
+ if (tcBorders == null)
+ tcBorders = new XElement(W.tcBorders,
+ new XElement(W.top, new XAttribute(W.val, "nil")),
+ new XElement(W.left, new XAttribute(W.val, "nil")),
+ new XElement(W.bottom, new XAttribute(W.val, "nil")),
+ new XElement(W.right, new XAttribute(W.val, "nil")));
+
+ XElement shd = GetCellShading(element);
+
+ //XElement hideMark = new XElement(W.hideMark);
+ XElement hideMark = null;
+
+ XElement tcMar = GetCellMargins(element);
+
+ XElement vAlign = new XElement(W.vAlign, new XAttribute(W.val, "center"));
+
+ return new XElement(W.tcPr,
+ gridSpan,
+ tcBorders,
+ shd,
+ tcMar,
+ vAlign,
+ hideMark);
+ }
+
+ private static XElement GetCellProperties(XElement element)
+ {
+ int? colspan = (int?)element.Attribute(XhtmlNoNamespace.colspan);
+ XElement gridSpan = null;
+ if (colspan != null)
+ gridSpan = new XElement(W.gridSpan,
+ new XAttribute(W.val, colspan));
+
+ XElement tblW = GetCellWidth(element);
+
+ XElement tcBorders = GetBlockContentBorders(element, W.tcBorders, false);
+
+ XElement shd = GetCellShading(element);
+
+ //XElement hideMark = new XElement(W.hideMark);
+ XElement hideMark = null;
+
+ XElement tcMar = GetCellMargins(element);
+
+ XElement vAlign = new XElement(W.vAlign, new XAttribute(W.val, "center"));
+
+ XElement vMerge = null;
+ if (element.Attribute("HtmlToWmlVMergeNoRestart") != null)
+ vMerge = new XElement(W.vMerge);
+ else
+ if (element.Attribute("HtmlToWmlVMergeRestart") != null)
+ vMerge = new XElement(W.vMerge,
+ new XAttribute(W.val, "restart"));
+
+ string vAlignValue = (string)element.Attribute(XhtmlNoNamespace.valign);
+ CssExpression verticalAlignmentProp = element.GetProp("vertical-align");
+ if (verticalAlignmentProp != null && verticalAlignmentProp.ToString() != "inherit")
+ vAlignValue = verticalAlignmentProp.ToString();
+ if (vAlignValue != null)
+ {
+ if (vAlignValue == "middle" || (vAlignValue != "top" && vAlignValue != "bottom"))
+ vAlignValue = "center";
+ vAlign = new XElement(W.vAlign, new XAttribute(W.val, vAlignValue));
+ }
+
+ return new XElement(W.tcPr,
+ tblW,
+ gridSpan,
+ vMerge,
+ tcBorders,
+ shd,
+ tcMar,
+ vAlign,
+ hideMark);
+ }
+
+ private static XElement GetCellHeaderProperties(XElement element)
+ {
+ //int? colspan = (int?)element.Attribute(Xhtml.colspan);
+ //XElement gridSpan = null;
+ //if (colspan != null)
+ // gridSpan = new XElement(W.gridSpan,
+ // new XAttribute(W.val, colspan));
+
+ XElement tblW = GetCellWidth(element);
+
+ XElement tcBorders = GetBlockContentBorders(element, W.tcBorders, false);
+
+ XElement shd = GetCellShading(element);
+
+ //XElement hideMark = new XElement(W.hideMark);
+ XElement hideMark = null;
+
+ XElement tcMar = GetCellMargins(element);
+
+ XElement vAlign = new XElement(W.vAlign, new XAttribute(W.val, "center"));
+
+ return new XElement(W.tcPr,
+ tblW,
+ tcBorders,
+ shd,
+ tcMar,
+ vAlign,
+ hideMark);
+ }
+
+ private static XElement GetCellShading(XElement element)
+ {
+ CssExpression backgroundColorProp = element.GetProp("background-color");
+ if (backgroundColorProp != null && (string)backgroundColorProp != "transparent")
+ {
+ XElement shd = new XElement(W.shd,
+ new XAttribute(W.val, "clear"),
+ new XAttribute(W.color, "auto"),
+ new XAttribute(W.fill, backgroundColorProp));
+ return shd;
+ }
+ return null;
+ }
+
+ private static XElement GetCellMargins(XElement element)
+ {
+ CssExpression topProp = element.GetProp("padding-top");
+ CssExpression leftProp = element.GetProp("padding-left");
+ CssExpression bottomProp = element.GetProp("padding-bottom");
+ CssExpression rightProp = element.GetProp("padding-right");
+ if ((long)topProp == 0 &&
+ (long)leftProp == 0 &&
+ (long)bottomProp == 0 &&
+ (long)rightProp == 0)
+ return null;
+ XElement top = null;
+ if (topProp != null)
+ top = new XElement(W.top,
+ new XAttribute(W._w, (long)(Twip)topProp),
+ new XAttribute(W.type, "dxa"));
+ XElement left = null;
+ if (leftProp != null)
+ left = new XElement(W.left,
+ new XAttribute(W._w, (long)(Twip)leftProp),
+ new XAttribute(W.type, "dxa"));
+ XElement bottom = null;
+ if (bottomProp != null)
+ bottom = new XElement(W.bottom,
+ new XAttribute(W._w, (long)(Twip)bottomProp),
+ new XAttribute(W.type, "dxa"));
+ XElement right = null;
+ if (rightProp != null)
+ right = new XElement(W.right,
+ new XAttribute(W._w, (long)(Twip)rightProp),
+ new XAttribute(W.type, "dxa"));
+ XElement tcMar = new XElement(W.tcMar,
+ top, left, bottom, right);
+ if (tcMar.Elements().Any())
+ return tcMar;
+ return null;
+ }
+
+#if false
+ <w:tcMar>
+ <w:top w:w="720"
+ w:type="dxa" />
+ <w:left w:w="720"
+ w:type="dxa" />
+ <w:bottom w:w="720"
+ w:type="dxa" />
+ <w:right w:w="720"
+ w:type="dxa" />
+ </w:tcMar>
+#endif
+
+ private static XElement GetTableCellSpacing(XElement element)
+ {
+ XElement table = element.AncestorsAndSelf(XhtmlNoNamespace.table).FirstOrDefault();
+ XElement tblCellSpacing = null;
+ if (table != null)
+ {
+ CssExpression borderCollapse = table.GetProp("border-collapse");
+ if (borderCollapse == null || (string)borderCollapse != "collapse")
+ {
+ // todo very incomplete
+ CssExpression borderSpacing = table.GetProp("border-spacing");
+ CssExpression marginTopProperty = element.GetProp("margin-top");
+ if (marginTopProperty == null)
+ marginTopProperty = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = CssTermType.Number, Unit = CssUnit.PT } } };
+ CssExpression marginBottomProperty = element.GetProp("margin-bottom");
+ if (marginBottomProperty == null)
+ marginBottomProperty = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = CssTermType.Number, Unit = CssUnit.PT } } };
+ Twip twips1 = (Twip)marginTopProperty;
+ Twip twips2 = (Twip)marginBottomProperty;
+ Twip minTwips = 15;
+ if (borderSpacing != null)
+ minTwips = (Twip)borderSpacing;
+ long twipToUse = Math.Max((long)twips1, (long)twips2);
+ twipToUse = Math.Max(twipToUse, (long)minTwips);
+ // have to divide twipToUse by 2 because border-spacing specifies the space between the border of once cell and its adjacent.
+ // tblCellSpacing specifies the distance between the border and the half way point between two cells.
+ long twipToUseOverTwo = (long)twipToUse / 2;
+ tblCellSpacing = new XElement(W.tblCellSpacing, new XAttribute(W._w, twipToUseOverTwo),
+ new XAttribute(W.type, "dxa"));
+ }
+
+ }
+ return tblCellSpacing;
+ }
+
+ private static XElement GetTableCellMargins(XElement element)
+ {
+ // todo very incomplete
+ XElement tblCellMar = XElement.Parse(
+@"<w:tblCellMar xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:top w:w='15'
+ w:type='dxa'/>
+ <w:left w:w='15'
+ w:type='dxa'/>
+ <w:bottom w:w='15'
+ w:type='dxa'/>
+ <w:right w:w='15'
+ w:type='dxa'/>
+</w:tblCellMar>");
+ tblCellMar.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
+ return tblCellMar;
+ }
+
+ private static XElement GetTableRowProperties(XElement element)
+ {
+ XElement trPr = null;
+ XElement table = element.AncestorsAndSelf(XhtmlNoNamespace.table).FirstOrDefault();
+ if (table != null)
+ {
+ CssExpression heightProperty = element.GetProp("height");
+ //long? maxCellHeight = element.Elements(Xhtml.td).Aggregate((long?)null,
+ // (XElement td, long? last) =>
+ // {
+ // Expression heightProp2 = td.GetProp("height");
+ // if (heightProp2 == null)
+ // return last;
+ // if (last == null)
+ // return (long)(Twip)heightProp2;
+ // return last + (long?)(long)(Twip)heightProp2;
+ // });
+ var cellHeights = element
+ .Elements(XhtmlNoNamespace.td)
+ .Select(td => td.GetProp("height"))
+ .Concat(new[] { heightProperty })
+ .Where(d => d != null)
+ .Select(e => (long)(Twip)e)
+ .ToList();
+ XElement trHeight = null;
+ if (cellHeights.Any())
+ {
+ long max = cellHeights.Max();
+ trHeight = new XElement(W.trHeight,
+ new XAttribute(W.val, max));
+ }
+
+ CssExpression borderCollapseProperty = table.GetProp("border-collapse");
+ XElement borderCollapse = null;
+ if (borderCollapseProperty != null && (string)borderCollapseProperty != "collapse")
+ borderCollapse = GetTableCellSpacing(element);
+
+ trPr = new XElement(W.trPr,
+ GetTableCellSpacing(element),
+ trHeight);
+ if (trPr.Elements().Any())
+ return trPr;
+ }
+ return trPr;
+ }
+
+ private static XAttribute GetXmlSpaceAttribute(string value)
+ {
+ if (value.StartsWith(" ") || value.EndsWith(" "))
+ return new XAttribute(XNamespace.Xml + "space", "preserve");
+ return null;
+ }
+
+
+ private static Dictionary<string, string> InstalledFonts = new Dictionary<string, string>
+ {
+ {"serif", "Times New Roman"},
+ {"sans-serif", "Arial"},
+ {"cursive", "Kunstler Script"},
+ {"fantasy", "Curlz MT"},
+ {"monospace", "Courier New"},
+
+ {"agency fb", "Agency FB"},
+ {"agencyfb", "Agency FB"},
+ {"aharoni", "Aharoni"},
+ {"algerian", "Algerian"},
+ {"andalus", "Andalus"},
+ {"angsana new", "Angsana New"},
+ {"angsananew", "Angsana New"},
+ {"angsanaupc", "AngsanaUPC"},
+ {"aparajita", "Aparajita"},
+ {"arabic typesetting", "Arabic Typesetting"},
+ {"arabictypesetting", "Arabic Typesetting"},
+ {"arial", "Arial"},
+ {"arial black", "Arial Black"},
+ {"arial narrow", "Arial Narrow"},
+ {"arial rounded mt bold", "Arial Rounded MT Bold"},
+ {"arial unicode ms", "Arial Unicode MS"},
+ {"arialblack", "Arial Black"},
+ {"arialnarrow", "Arial Narrow"},
+ {"arialroundedmtbold", "Arial Rounded MT Bold"},
+ {"arialunicodems", "Arial Unicode MS"},
+ {"baskerville old face", "Baskerville Old Face"},
+ {"baskervilleoldface", "Baskerville Old Face"},
+ {"batang", "Batang"},
+ {"batangche", "BatangChe"},
+ {"bauhaus 93", "Bauhaus 93"},
+ {"bauhaus93", "Bauhaus 93"},
+ {"bell mt", "Bell MT"},
+ {"bellmt", "Bell MT"},
+ {"berlin sans fb", "Berlin Sans FB"},
+ {"berlin sans fb demi", "Berlin Sans FB Demi"},
+ {"berlinsansfb", "Berlin Sans FB"},
+ {"berlinsansfbdemi", "Berlin Sans FB Demi"},
+ {"bernard mt condensed", "Bernard MT Condensed"},
+ {"bernardmtcondensed", "Bernard MT Condensed"},
+ {"blackadder itc", "Blackadder ITC"},
+ {"blackadderitc", "Blackadder ITC"},
+ {"bodoni mt", "Bodoni MT"},
+ {"bodoni mt black", "Bodoni MT Black"},
+ {"bodoni mt condensed", "Bodoni MT Condensed"},
+ {"bodoni mt poster compressed", "Bodoni MT Poster Compressed"},
+ {"bodonimt", "Bodoni MT"},
+ {"bodonimtblack", "Bodoni MT Black"},
+ {"bodonimtcondensed", "Bodoni MT Condensed"},
+ {"bodonimtpostercompressed", "Bodoni MT Poster Compressed"},
+ {"book antiqua", "Book Antiqua"},
+ {"bookantiqua", "Book Antiqua"},
+ {"bookman old style", "Bookman Old Style"},
+ {"bookmanoldstyle", "Bookman Old Style"},
+ {"bookshelf symbol 7", "Bookshelf Symbol 7"},
+ {"bookshelfsymbol7", "Bookshelf Symbol 7"},
+ {"bradley hand itc", "Bradley Hand ITC"},
+ {"bradleyhanditc", "Bradley Hand ITC"},
+ {"britannic bold", "Britannic Bold"},
+ {"britannicbold", "Britannic Bold"},
+ {"broadway", "Broadway"},
+ {"browallia new", "Browallia New"},
+ {"browallianew", "Browallia New"},
+ {"browalliaupc", "BrowalliaUPC"},
+ {"brush script mt", "Brush Script MT"},
+ {"brushscriptmt", "Brush Script MT"},
+ {"calibri", "Calibri"},
+ {"californian fb", "Californian FB"},
+ {"californianfb", "Californian FB"},
+ {"calisto mt", "Calisto MT"},
+ {"calistomt", "Calisto MT"},
+ {"cambria", "Cambria"},
+ {"cambria math", "Cambria Math"},
+ {"cambriamath", "Cambria Math"},
+ {"candara", "Candara"},
+ {"castellar", "Castellar"},
+ {"centaur", "Centaur"},
+ {"century", "Century"},
+ {"century gothic", "Century Gothic"},
+ {"century schoolbook", "Century Schoolbook"},
+ {"centurygothic", "Century Gothic"},
+ {"centuryschoolbook", "Century Schoolbook"},
+ {"chiller", "Chiller"},
+ {"colonna mt", "Colonna MT"},
+ {"colonnamt", "Colonna MT"},
+ {"comic sans ms", "Comic Sans MS"},
+ {"comicsansms", "Comic Sans MS"},
+ {"consolas", "Consolas"},
+ {"constantia", "Constantia"},
+ {"cooper black", "Cooper Black"},
+ {"cooperblack", "Cooper Black"},
+ {"copperplate gothic bold", "Copperplate Gothic Bold"},
+ {"copperplate gothic light", "Copperplate Gothic Light"},
+ {"copperplategothicbold", "Copperplate Gothic Bold"},
+ {"copperplategothiclight", "Copperplate Gothic Light"},
+ {"corbel", "Corbel"},
+ {"cordia new", "Cordia New"},
+ {"cordianew", "Cordia New"},
+ {"cordiaupc", "CordiaUPC"},
+ {"courier new", "Courier New"},
+ {"couriernew", "Courier New"},
+ {"curlz mt", "Curlz MT"},
+ {"curlzmt", "Curlz MT"},
+ {"daunpenh", "DaunPenh"},
+ {"david", "David"},
+ {"dfkai-sb", "DFKai-SB"},
+ {"dilleniaupc", "DilleniaUPC"},
+ {"dokchampa", "DokChampa"},
+ {"dotum", "Dotum"},
+ {"dotumche", "DotumChe"},
+ {"ebrima", "Ebrima"},
+ {"edwardian script itc", "Edwardian Script ITC"},
+ {"edwardianscriptitc", "Edwardian Script ITC"},
+ {"elephant", "Elephant"},
+ {"engravers mt", "Engravers MT"},
+ {"engraversmt", "Engravers MT"},
+ {"eras bold itc", "Eras Bold ITC"},
+ {"eras demi itc", "Eras Demi ITC"},
+ {"eras light itc", "Eras Light ITC"},
+ {"eras medium itc", "Eras Medium ITC"},
+ {"erasbolditc", "Eras Bold ITC"},
+ {"erasdemiitc", "Eras Demi ITC"},
+ {"eraslightitc", "Eras Light ITC"},
+ {"erasmediumitc", "Eras Medium ITC"},
+ {"estrangelo edessa", "Estrangelo Edessa"},
+ {"estrangeloedessa", "Estrangelo Edessa"},
+ {"eucrosiaupc", "EucrosiaUPC"},
+ {"euphemia", "Euphemia"},
+ {"fangsong", "FangSong"},
+ {"felix titling", "Felix Titling"},
+ {"felixtitling", "Felix Titling"},
+ {"footlight mt light", "Footlight MT Light"},
+ {"footlightmtlight", "Footlight MT Light"},
+ {"forte", "Forte"},
+ {"franklin gothic book", "Franklin Gothic Book"},
+ {"franklin gothic demi", "Franklin Gothic Demi"},
+ {"franklin gothic demi cond", "Franklin Gothic Demi Cond"},
+ {"franklin gothic heavy", "Franklin Gothic Heavy"},
+ {"franklin gothic medium", "Franklin Gothic Medium"},
+ {"franklin gothic medium cond", "Franklin Gothic Medium Cond"},
+ {"franklingothicbook", "Franklin Gothic Book"},
+ {"franklingothicdemi", "Franklin Gothic Demi"},
+ {"franklingothicdemicond", "Franklin Gothic Demi Cond"},
+ {"franklingothicheavy", "Franklin Gothic Heavy"},
+ {"franklingothicmedium", "Franklin Gothic Medium"},
+ {"franklingothicmediumcond", "Franklin Gothic Medium Cond"},
+ {"frankruehl", "FrankRuehl"},
+ {"freesiaupc", "FreesiaUPC"},
+ {"freestyle script", "Freestyle Script"},
+ {"freestylescript", "Freestyle Script"},
+ {"french script mt", "French Script MT"},
+ {"frenchscriptmt", "French Script MT"},
+ {"gabriola", "Gabriola"},
+ {"garamond", "Garamond"},
+ {"gautami", "Gautami"},
+ {"georgia", "Georgia"},
+ {"gigi", "Gigi"},
+ {"gill sans mt", "Gill Sans MT"},
+ {"gill sans mt condensed", "Gill Sans MT Condensed"},
+ {"gill sans mt ext condensed bold", "Gill Sans MT Ext Condensed Bold"},
+ {"gill sans ultra bold", "Gill Sans Ultra Bold"},
+ {"gill sans ultra bold condensed", "Gill Sans Ultra Bold Condensed"},
+ {"gillsansmt", "Gill Sans MT"},
+ {"gillsansmtcondensed", "Gill Sans MT Condensed"},
+ {"gillsansmtextcondensedbold", "Gill Sans MT Ext Condensed Bold"},
+ {"gillsansultrabold", "Gill Sans Ultra Bold"},
+ {"gillsansultraboldcondensed", "Gill Sans Ultra Bold Condensed"},
+ {"gisha", "Gisha"},
+ {"gloucester mt extra condensed", "Gloucester MT Extra Condensed"},
+ {"gloucestermtextracondensed", "Gloucester MT Extra Condensed"},
+ {"goudy old style", "Goudy Old Style"},
+ {"goudy stout", "Goudy Stout"},
+ {"goudyoldstyle", "Goudy Old Style"},
+ {"goudystout", "Goudy Stout"},
+ {"gulim", "Gulim"},
+ {"gulimche", "GulimChe"},
+ {"gungsuh", "Gungsuh"},
+ {"gungsuhche", "GungsuhChe"},
+ {"haettenschweiler", "Haettenschweiler"},
+ {"harlow solid italic", "Harlow Solid Italic"},
+ {"harlowsoliditalic", "Harlow Solid Italic"},
+ {"harrington", "Harrington"},
+ {"high tower text", "High Tower Text"},
+ {"hightowertext", "High Tower Text"},
+ {"impact", "Impact"},
+ {"imprint mt shadow", "Imprint MT Shadow"},
+ {"imprintmtshadow", "Imprint MT Shadow"},
+ {"informal roman", "Informal Roman"},
+ {"informalroman", "Informal Roman"},
+ {"irisupc", "IrisUPC"},
+ {"iskoola pota", "Iskoola Pota"},
+ {"iskoolapota", "Iskoola Pota"},
+ {"jasmineupc", "JasmineUPC"},
+ {"jokerman", "Jokerman"},
+ {"juice itc", "Juice ITC"},
+ {"juiceitc", "Juice ITC"},
+ {"kaiti", "KaiTi"},
+ {"kalinga", "Kalinga"},
+ {"kartika", "Kartika"},
+ {"khmer ui", "Khmer UI"},
+ {"khmerui", "Khmer UI"},
+ {"kodchiangupc", "KodchiangUPC"},
+ {"kokila", "Kokila"},
+ {"kristen itc", "Kristen ITC"},
+ {"kristenitc", "Kristen ITC"},
+ {"kunstler script", "Kunstler Script"},
+ {"kunstlerscript", "Kunstler Script"},
+ {"lao ui", "Lao UI"},
+ {"laoui", "Lao UI"},
+ {"latha", "Latha"},
+ {"leelawadee", "Leelawadee"},
+ {"levenim mt", "Levenim MT"},
+ {"levenimmt", "Levenim MT"},
+ {"lilyupc", "LilyUPC"},
+ {"lucida bright", "Lucida Bright"},
+ {"lucida calligraphy", "Lucida Calligraphy"},
+ {"lucida console", "Lucida Console"},
+ {"lucida fax", "Lucida Fax"},
+ {"lucida handwriting", "Lucida Handwriting"},
+ {"lucida sans", "Lucida Sans"},
+ {"lucida sans typewriter", "Lucida Sans Typewriter"},
+ {"lucida sans unicode", "Lucida Sans Unicode"},
+ {"lucidabright", "Lucida Bright"},
+ {"lucidacalligraphy", "Lucida Calligraphy"},
+ {"lucidaconsole", "Lucida Console"},
+ {"lucidafax", "Lucida Fax"},
+ {"lucidahandwriting", "Lucida Handwriting"},
+ {"lucidasans", "Lucida Sans"},
+ {"lucidasanstypewriter", "Lucida Sans Typewriter"},
+ {"lucidasansunicode", "Lucida Sans Unicode"},
+ {"magneto", "Magneto"},
+ {"maiandra gd", "Maiandra GD"},
+ {"maiandragd", "Maiandra GD"},
+ {"malgun gothic", "Malgun Gothic"},
+ {"malgungothic", "Malgun Gothic"},
+ {"mangal", "Mangal"},
+ {"marlett", "Marlett"},
+ {"matura mt script capitals", "Matura MT Script Capitals"},
+ {"maturamtscriptcapitals", "Matura MT Script Capitals"},
+ {"meiryo", "Meiryo"},
+ {"meiryo ui", "Meiryo UI"},
+ {"meiryoui", "Meiryo UI"},
+ {"microsoft himalaya", "Microsoft Himalaya"},
+ {"microsoft jhenghei", "Microsoft JhengHei"},
+ {"microsoft new tai lue", "Microsoft New Tai Lue"},
+ {"microsoft phagspa", "Microsoft PhagsPa"},
+ {"microsoft sans serif", "Microsoft Sans Serif"},
+ {"microsoft tai le", "Microsoft Tai Le"},
+ {"microsoft uighur", "Microsoft Uighur"},
+ {"microsoft yahei", "Microsoft YaHei"},
+ {"microsoft yi baiti", "Microsoft Yi Baiti"},
+ {"microsofthimalaya", "Microsoft Himalaya"},
+ {"microsoftjhenghei", "Microsoft JhengHei"},
+ {"microsoftnewtailue", "Microsoft New Tai Lue"},
+ {"microsoftphagspa", "Microsoft PhagsPa"},
+ {"microsoftsansserif", "Microsoft Sans Serif"},
+ {"microsofttaile", "Microsoft Tai Le"},
+ {"microsoftuighur", "Microsoft Uighur"},
+ {"microsoftyahei", "Microsoft YaHei"},
+ {"microsoftyibaiti", "Microsoft Yi Baiti"},
+ {"mingliu", "MingLiU"},
+ {"mingliu_hkscs", "MingLiU_HKSCS"},
+ {"mingliu_hkscs-extb", "MingLiU_HKSCS-ExtB"},
+ {"mingliu-extb", "MingLiU-ExtB"},
+ {"miriam", "Miriam"},
+ {"miriam fixed", "Miriam Fixed"},
+ {"miriamfixed", "Miriam Fixed"},
+ {"mistral", "Mistral"},
+ {"modern no. 20", "Modern No. 20"},
+ {"modernno.20", "Modern No. 20"},
+ {"mongolian baiti", "Mongolian Baiti"},
+ {"mongolianbaiti", "Mongolian Baiti"},
+ {"monotype corsiva", "Monotype Corsiva"},
+ {"monotypecorsiva", "Monotype Corsiva"},
+ {"moolboran", "MoolBoran"},
+ {"ms gothic", "MS Gothic"},
+ {"ms mincho", "MS Mincho"},
+ {"ms pgothic", "MS PGothic"},
+ {"ms pmincho", "MS PMincho"},
+ {"ms reference sans serif", "MS Reference Sans Serif"},
+ {"ms reference specialty", "MS Reference Specialty"},
+ {"ms ui gothic", "MS UI Gothic"},
+ {"msgothic", "MS Gothic"},
+ {"msmincho", "MS Mincho"},
+ {"mspgothic", "MS PGothic"},
+ {"mspmincho", "MS PMincho"},
+ {"msreferencesansserif", "MS Reference Sans Serif"},
+ {"msreferencespecialty", "MS Reference Specialty"},
+ {"msuigothic", "MS UI Gothic"},
+ {"mt extra", "MT Extra"},
+ {"mtextra", "MT Extra"},
+ {"mv boli", "MV Boli"},
+ {"mvboli", "MV Boli"},
+ {"narkisim", "Narkisim"},
+ {"niagara engraved", "Niagara Engraved"},
+ {"niagara solid", "Niagara Solid"},
+ {"niagaraengraved", "Niagara Engraved"},
+ {"niagarasolid", "Niagara Solid"},
+ {"nsimsun", "NSimSun"},
+ {"nyala", "Nyala"},
+ {"ocr a extended", "OCR A Extended"},
+ {"ocraextended", "OCR A Extended"},
+ {"old english text mt", "Old English Text MT"},
+ {"oldenglishtextmt", "Old English Text MT"},
+ {"onyx", "Onyx"},
+ {"palace script mt", "Palace Script MT"},
+ {"palacescriptmt", "Palace Script MT"},
+ {"palatino linotype", "Palatino Linotype"},
+ {"palatinolinotype", "Palatino Linotype"},
+ {"papyrus", "Papyrus"},
+ {"parchment", "Parchment"},
+ {"perpetua", "Perpetua"},
+ {"perpetua titling mt", "Perpetua Titling MT"},
+ {"perpetuatitlingmt", "Perpetua Titling MT"},
+ {"plantagenet cherokee", "Plantagenet Cherokee"},
+ {"plantagenetcherokee", "Plantagenet Cherokee"},
+ {"playbill", "Playbill"},
+ {"pmingliu", "PMingLiU"},
+ {"pmingliu-extb", "PMingLiU-ExtB"},
+ {"poor richard", "Poor Richard"},
+ {"poorrichard", "Poor Richard"},
+ {"pristina", "Pristina"},
+ {"raavi", "Raavi"},
+ {"rage italic", "Rage Italic"},
+ {"rageitalic", "Rage Italic"},
+ {"ravie", "Ravie"},
+ {"rockwell", "Rockwell"},
+ {"rockwell condensed", "Rockwell Condensed"},
+ {"rockwell extra bold", "Rockwell Extra Bold"},
+ {"rockwellcondensed", "Rockwell Condensed"},
+ {"rockwellextrabold", "Rockwell Extra Bold"},
+ {"rod", "Rod"},
+ {"sakkal majalla", "Sakkal Majalla"},
+ {"sakkalmajalla", "Sakkal Majalla"},
+ {"script mt bold", "Script MT Bold"},
+ {"scriptmtbold", "Script MT Bold"},
+ {"segoe print", "Segoe Print"},
+ {"segoe script", "Segoe Script"},
+ {"segoe ui", "Segoe UI"},
+ {"segoe ui light", "Segoe UI Light"},
+ {"segoe ui semibold", "Segoe UI Semibold"},
+ {"segoe ui symbol", "Segoe UI Symbol"},
+ {"segoeprint", "Segoe Print"},
+ {"segoescript", "Segoe Script"},
+ {"segoeui", "Segoe UI"},
+ {"segoeuilight", "Segoe UI Light"},
+ {"segoeuisemibold", "Segoe UI Semibold"},
+ {"segoeuisymbol", "Segoe UI Symbol"},
+ {"shonar bangla", "Shonar Bangla"},
+ {"shonarbangla", "Shonar Bangla"},
+ {"showcard gothic", "Showcard Gothic"},
+ {"showcardgothic", "Showcard Gothic"},
+ {"shruti", "Shruti"},
+ {"simhei", "SimHei"},
+ {"simplified arabic", "Simplified Arabic"},
+ {"simplified arabic fixed", "Simplified Arabic Fixed"},
+ {"simplifiedarabic", "Simplified Arabic"},
+ {"simplifiedarabicfixed", "Simplified Arabic Fixed"},
+ {"simsun", "SimSun"},
+ {"simsun-extb", "SimSun-ExtB"},
+ {"snap itc", "Snap ITC"},
+ {"snapitc", "Snap ITC"},
+ {"stencil", "Stencil"},
+ {"swgamekeys mt", "SWGamekeys MT"},
+ {"swgamekeysmt", "SWGamekeys MT"},
+ {"swmacro", "SWMacro"},
+ {"sylfaen", "Sylfaen"},
+ {"symbol", "Symbol"},
+ {"tahoma", "Tahoma"},
+ {"tempus sans itc", "Tempus Sans ITC"},
+ {"tempussansitc", "Tempus Sans ITC"},
+ {"times new roman", "Times New Roman"},
+ {"timesnewroman", "Times New Roman"},
+ {"traditional arabic", "Traditional Arabic"},
+ {"traditionalarabic", "Traditional Arabic"},
+ {"trebuchet ms", "Trebuchet MS"},
+ {"trebuchetms", "Trebuchet MS"},
+ {"tunga", "Tunga"},
+ {"tw cen mt", "Tw Cen MT"},
+ {"tw cen mt condensed", "Tw Cen MT Condensed"},
+ {"tw cen mt condensed extra bold", "Tw Cen MT Condensed Extra Bold"},
+ {"twcenmt", "Tw Cen MT"},
+ {"twcenmtcondensed", "Tw Cen MT Condensed"},
+ {"twcenmtcondensedextrabold", "Tw Cen MT Condensed Extra Bold"},
+ {"utsaah", "Utsaah"},
+ {"vani", "Vani"},
+ {"verdana", "Verdana"},
+ {"vijaya", "Vijaya"},
+ {"viner hand itc", "Viner Hand ITC"},
+ {"vinerhanditc", "Viner Hand ITC"},
+ {"vivaldi", "Vivaldi"},
+ {"vladimir script", "Vladimir Script"},
+ {"vladimirscript", "Vladimir Script"},
+ {"vrinda", "Vrinda"},
+ {"webdings", "Webdings"},
+ {"wide latin", "Wide Latin"},
+ {"widelatin", "Wide Latin"},
+ {"wingdings", "Wingdings"},
+ {"wingdings 2", "Wingdings 2"},
+ {"wingdings 3", "Wingdings 3"},
+ {"wingdings2", "Wingdings 2"},
+ {"wingdings3", "Wingdings 3"},
+ };
+
+ private static TPoint? GetUsedSizeFromFontSizeProperty(CssExpression fontSize)
+ {
+ if (fontSize == null)
+ return null;
+ if (fontSize.Terms.Count() == 1)
+ {
+ CssTerm term = fontSize.Terms.First();
+ double size = 0;
+ if (term.Unit == CssUnit.PT)
+ {
+ if (double.TryParse(term.Value, out size))
+ return new TPoint(size);
+ return null;
+ }
+ return null;
+ }
+ return null;
+ }
+
+ private static string GetUsedFontFromFontFamilyProperty(CssExpression fontFamily)
+ {
+ if (fontFamily == null)
+ return null;
+ string fullFontFamily = fontFamily.Terms.Select(t => t + " ").StringConcatenate().Trim();
+ string lcfont = fullFontFamily.ToLower();
+ if (InstalledFonts.ContainsKey(lcfont))
+ return InstalledFonts[lcfont];
+ return null;
+ }
+
+ private static XElement GetBackgroundProperty(XElement element)
+ {
+ CssExpression color = element.GetProp("background-color");
+
+ // todo this really should test against default background color
+ if (color.ToString() != "transparent")
+ {
+ string hexString = color.ToString();
+ XElement shd = new XElement(W.shd,
+ new XAttribute(W.val, "clear"),
+ new XAttribute(W.color, "auto"),
+ new XAttribute(W.fill, hexString));
+ return shd;
+ }
+ return null;
+ }
+
+ }
+
+ public class PictureId
+ {
+ public int Id;
+ }
+
+ class HtmlToWmlFontUpdater
+ {
+ public static void UpdateFontsPart(WordprocessingDocument wDoc, XElement html, HtmlToWmlConverterSettings settings)
+ {
+ XDocument fontXDoc = wDoc.MainDocumentPart.FontTablePart.GetXDocument();
+
+ PtUtils.AddElementIfMissing(fontXDoc,
+ fontXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading1")
+ .FirstOrDefault(),
+@"<w:font w:name='Verdana' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:panose1 w:val='020B0604030504040204'/>
+ <w:charset w:val='00'/>
+ <w:family w:val='swiss'/>
+ <w:pitch w:val='variable'/>
+ <w:sig w:usb0='A10006FF'
+ w:usb1='4000205B'
+ w:usb2='00000010'
+ w:usb3='00000000'
+ w:csb0='0000019F'
+ w:csb1='00000000'/>
+</w:font>");
+
+ wDoc.MainDocumentPart.FontTablePart.PutXDocument();
+ }
+ }
+
+ class NumberingUpdater
+ {
+ public static void InitializeNumberingPart(WordprocessingDocument wDoc)
+ {
+ NumberingDefinitionsPart numberingPart = wDoc.MainDocumentPart.NumberingDefinitionsPart;
+ if (numberingPart == null)
+ {
+ wDoc.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>();
+ XDocument npXDoc = new XDocument(
+ new XElement(W.numbering,
+ new XAttribute(XNamespace.Xmlns + "w", W.w)));
+ wDoc.MainDocumentPart.NumberingDefinitionsPart.PutXDocument(npXDoc);
+ }
+ }
+
+ public static void GetNextNumId(WordprocessingDocument wDoc, out int nextNumId)
+ {
+ InitializeNumberingPart(wDoc);
+ NumberingDefinitionsPart numberingPart = wDoc.MainDocumentPart.NumberingDefinitionsPart;
+ XDocument numberingXDoc = numberingPart.GetXDocument();
+ nextNumId = numberingXDoc.Root.Elements(W.num).Attributes(W.numId).Select(ni => (int)ni).Concat(new[] { 1 }).Max();
+ }
+
+ // decimal, lowerLetter
+ private static string OrderedListAbstractXml =
+@"<w:abstractNum w:abstractNumId='{0}' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:multiLevelType w:val='multilevel'/>
+ <w:tmpl w:val='7D26959A'/>
+ <w:lvl w:ilvl='0'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{1}'/>
+ <w:lvlText w:val='%1.'/>
+ <w:lvlJc w:val='{2}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='720'/>
+ </w:tabs>
+ <w:ind w:left='720'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='1'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{3}'/>
+ <w:lvlText w:val='%2.'/>
+ <w:lvlJc w:val='{4}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='1440'/>
+ </w:tabs>
+ <w:ind w:left='1440'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='2'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{5}'/>
+ <w:lvlText w:val='%3.'/>
+ <w:lvlJc w:val='{6}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='2160'/>
+ </w:tabs>
+ <w:ind w:left='2160'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='3'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{7}'/>
+ <w:lvlText w:val='%4.'/>
+ <w:lvlJc w:val='{8}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='2880'/>
+ </w:tabs>
+ <w:ind w:left='2880'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='4'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{9}'/>
+ <w:lvlText w:val='%5.'/>
+ <w:lvlJc w:val='{10}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='3600'/>
+ </w:tabs>
+ <w:ind w:left='3600'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='5'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{11}'/>
+ <w:lvlText w:val='%6.'/>
+ <w:lvlJc w:val='{12}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='4320'/>
+ </w:tabs>
+ <w:ind w:left='4320'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='6'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{13}'/>
+ <w:lvlText w:val='%7.'/>
+ <w:lvlJc w:val='{14}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='5040'/>
+ </w:tabs>
+ <w:ind w:left='5040'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='7'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{15}'/>
+ <w:lvlText w:val='%8.'/>
+ <w:lvlJc w:val='{16}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='5760'/>
+ </w:tabs>
+ <w:ind w:left='5760'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ <w:lvl w:ilvl='8'
+ w:tentative='1'>
+ <w:start w:val='1'/>
+ <w:numFmt w:val='{17}'/>
+ <w:lvlText w:val='%9.'/>
+ <w:lvlJc w:val='{18}'/>
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num'
+ w:pos='6480'/>
+ </w:tabs>
+ <w:ind w:left='6480'
+ w:hanging='360'/>
+ </w:pPr>
+ </w:lvl>
+ </w:abstractNum>";
+
+ private static string BulletAbstractXml =
+@"<w:abstractNum w:abstractNumId='{0}' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:multiLevelType w:val='multilevel' />
+ <w:tmpl w:val='02BEA0DA' />
+ <w:lvl w:ilvl='0'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='720' />
+ </w:tabs>
+ <w:ind w:left='720' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Symbol' w:hAnsi='Symbol' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='o' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='1440' />
+ </w:tabs>
+ <w:ind w:left='1440' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Courier New' w:hAnsi='Courier New' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='2' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='2160' />
+ </w:tabs>
+ <w:ind w:left='2160' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='3' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='2880' />
+ </w:tabs>
+ <w:ind w:left='2880' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='4' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='3600' />
+ </w:tabs>
+ <w:ind w:left='3600' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='5' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='4320' />
+ </w:tabs>
+ <w:ind w:left='4320' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='6' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='5040' />
+ </w:tabs>
+ <w:ind w:left='5040' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='7' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='5760' />
+ </w:tabs>
+ <w:ind w:left='5760' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+ <w:lvl w:ilvl='8' w:tentative='1'>
+ <w:start w:val='1' />
+ <w:numFmt w:val='bullet' />
+ <w:lvlText w:val='' />
+ <w:lvlJc w:val='left' />
+ <w:pPr>
+ <w:tabs>
+ <w:tab w:val='num' w:pos='6480' />
+ </w:tabs>
+ <w:ind w:left='6480' w:hanging='360' />
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Wingdings' w:hAnsi='Wingdings' w:hint='default' />
+ <w:sz w:val='20' />
+ </w:rPr>
+ </w:lvl>
+</w:abstractNum>";
+
+ public static void UpdateNumberingPart(WordprocessingDocument wDoc, XElement html, HtmlToWmlConverterSettings settings)
+ {
+ InitializeNumberingPart(wDoc);
+ NumberingDefinitionsPart numberingPart = wDoc.MainDocumentPart.NumberingDefinitionsPart;
+ XDocument numberingXDoc = numberingPart.GetXDocument();
+ int nextAbstractId, nextNumId;
+ nextNumId = numberingXDoc.Root.Elements(W.num).Attributes(W.numId).Select(ni => (int)ni).Concat(new[] { 1 }).Max();
+ nextAbstractId = numberingXDoc.Root.Elements(W.abstractNum).Attributes(W.abstractNumId).Select(ani => (int)ani).Concat(new[] { 0 }).Max();
+ var numberingElements = html.DescendantsAndSelf().Where(d => d.Name == XhtmlNoNamespace.ol || d.Name == XhtmlNoNamespace.ul).ToList();
+
+ Dictionary<int, int> numToAbstractNum = new Dictionary<int, int>();
+
+ // add abstract numbering elements
+ int currentNumId = nextNumId;
+ int currentAbstractId = nextAbstractId;
+ foreach (var list in numberingElements)
+ {
+ HtmlToWmlConverterCore.NumberedItemAnnotation nia = list.Annotation<HtmlToWmlConverterCore.NumberedItemAnnotation>();
+ if (!numToAbstractNum.ContainsKey(nia.numId))
+ {
+ numToAbstractNum.Add(nia.numId, currentAbstractId);
+ if (list.Name == XhtmlNoNamespace.ul)
+ {
+ XElement bulletAbstract = XElement.Parse(String.Format(BulletAbstractXml, currentAbstractId++));
+ numberingXDoc.Root.Add(bulletAbstract);
+ }
+ if (list.Name == XhtmlNoNamespace.ol)
+ {
+ string[] numFmt = new string[9];
+ string[] just = new string[9];
+ for (int i = 0; i < numFmt.Length; ++i)
+ {
+ numFmt[i] = "decimal";
+ just[i] = "left";
+ XElement itemAtLevel = numberingElements
+ .FirstOrDefault(nf =>
+ {
+ HtmlToWmlConverterCore.NumberedItemAnnotation n = nf.Annotation<HtmlToWmlConverterCore.NumberedItemAnnotation>();
+ if (n != null && n.numId == nia.numId && n.ilvl == i)
+ return true;
+ return false;
+ });
+ if (itemAtLevel != null)
+ {
+ HtmlToWmlConverterCore.NumberedItemAnnotation thisLevelNia = itemAtLevel.Annotation<HtmlToWmlConverterCore.NumberedItemAnnotation>();
+ string thisLevelNumFmt = thisLevelNia.listStyleType;
+ if (thisLevelNumFmt == "lower-alpha" || thisLevelNumFmt == "lower-latin")
+ {
+ numFmt[i] = "lowerLetter";
+ //just[i] = "left";
+ }
+ if (thisLevelNumFmt == "upper-alpha" || thisLevelNumFmt == "upper-latin")
+ {
+ numFmt[i] = "upperLetter";
+ //just[i] = "left";
+ }
+ if (thisLevelNumFmt == "decimal-leading-zero")
+ {
+ numFmt[i] = "decimalZero";
+ //just[i] = "left";
+ }
+ if (thisLevelNumFmt == "lower-roman")
+ {
+ numFmt[i] = "lowerRoman";
+ just[i] = "right";
+ }
+ if (thisLevelNumFmt == "upper-roman")
+ {
+ numFmt[i] = "upperRoman";
+ just[i] = "right";
+ }
+ }
+ }
+
+ XElement simpleNumAbstract = XElement.Parse(String.Format(OrderedListAbstractXml, currentAbstractId++,
+ numFmt[0], just[0], numFmt[1], just[1], numFmt[2], just[2], numFmt[3], just[3], numFmt[4], just[4], numFmt[5], just[5], numFmt[6], just[6], numFmt[7], just[7], numFmt[8], just[8]));
+ numberingXDoc.Root.Add(simpleNumAbstract);
+ }
+ }
+ }
+
+ foreach (var list in numToAbstractNum)
+ {
+ numberingXDoc.Root.Add(
+ new XElement(W.num, new XAttribute(W.numId, list.Key),
+ new XElement(W.abstractNumId, new XAttribute(W.val, list.Value))));
+ }
+
+ wDoc.MainDocumentPart.NumberingDefinitionsPart.PutXDocument();
+#if false
+ <w:num w:numId='1'>
+ <w:abstractNumId w:val='0'/>
+ </w:num>
+#endif
+ }
+ }
+
+ class StylesUpdater
+ {
+ public static void UpdateStylesPart(
+ WordprocessingDocument wDoc,
+ XElement html,
+ HtmlToWmlConverterSettings settings,
+ CssDocument defaultCssDoc,
+ CssDocument authorCssDoc,
+ CssDocument userCssDoc)
+ {
+ XDocument styleXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+
+ if (settings.DefaultSpacingElement != null)
+ {
+ XElement spacingElement = styleXDoc.Root.Elements(W.docDefaults).Elements(W.pPrDefault).Elements(W.pPr).Elements(W.spacing).FirstOrDefault();
+ if (spacingElement != null)
+ spacingElement.ReplaceWith(settings.DefaultSpacingElement);
+ }
+
+ var classes = html
+ .DescendantsAndSelf()
+ .Where(d => d.Attribute(XhtmlNoNamespace._class) != null && ((string)d.Attribute(XhtmlNoNamespace._class)).Split().Length == 1)
+ .Select(d => (string)d.Attribute(XhtmlNoNamespace._class));
+
+ foreach (var item in classes)
+ {
+ //string item = "ms-rteStyle-Byline";
+ foreach (var ruleSet in authorCssDoc.RuleSets)
+ {
+ var selector = ruleSet.Selectors.Where(
+ sel =>
+ {
+ bool found = sel.SimpleSelectors.Count() == 1 &&
+ sel.SimpleSelectors.First().Class == item &&
+ (sel.SimpleSelectors.First().ElementName == "" ||
+ sel.SimpleSelectors.First().ElementName == null);
+ return found;
+ }).FirstOrDefault();
+ var color = ruleSet.Declarations.FirstOrDefault(d => d.Name == "color");
+ if (selector != null)
+ {
+ //Console.WriteLine("found ruleset and selector for {0}", item);
+ string styleName = item.ToLower();
+ XElement newStyle = new XElement(W.style,
+ new XAttribute(W.type, "paragraph"),
+ new XAttribute(W.customStyle, "1"),
+ new XAttribute(W.styleId, styleName),
+ new XElement(W.name, new XAttribute(W.val, styleName)),
+ new XElement(W.basedOn, new XAttribute(W.val, "Normal")),
+ new XElement(W.pPr,
+ new XElement(W.spacing, new XAttribute(W.before, "100"),
+ new XAttribute(W.beforeAutospacing, "1"),
+ new XAttribute(W.after, "100"),
+ new XAttribute(W.afterAutospacing, "1"),
+ new XAttribute(W.line, "240"),
+ new XAttribute(W.lineRule, "auto"))),
+ new XElement(W.rPr,
+ new XElement(W.rFonts, new XAttribute(W.ascii, "Times New Roman"),
+ new XAttribute(W.eastAsiaTheme, "minorEastAsia"),
+ new XAttribute(W.hAnsi, "Times New Roman"),
+ new XAttribute(W.cs, "Times New Roman")),
+ color != null ? new XElement(W.color, new XAttribute(W.val, "this should be a color")) : null,
+ new XElement(W.sz, new XAttribute(W.val, "24")),
+ new XElement(W.szCs, new XAttribute(W.val, "24"))));
+ if (styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && ((string)e.Attribute(W.styleId)).ToLower() == styleName)
+ .FirstOrDefault() == null)
+ styleXDoc.Root.Add(newStyle);
+ }
+ }
+ }
+
+ if (html.Descendants(XhtmlNoNamespace.h1).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading1")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading1'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 1'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading1Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='480'
+ w:after='0'/>
+ <w:outlineLvl w:val='0'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:color w:val='365F91'
+ w:themeColor='accent1'
+ w:themeShade='BF'/>
+ <w:sz w:val='28'/>
+ <w:szCs w:val='28'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h2).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading2")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading2'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 2'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading2Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='1'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:color w:val='4F81BD'
+ w:themeColor='accent1'/>
+ <w:sz w:val='26'/>
+ <w:szCs w:val='26'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h3).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading3")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading3'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 3'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading3Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='2'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:color w:val='4F81BD'
+ w:themeColor='accent1'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h4).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading4")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading4'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 4'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading4Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='3'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='4F81BD'
+ w:themeColor='accent1'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h5).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading5")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading5'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 5'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading5Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='4'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:color w:val='243F60'
+ w:themeColor='accent1'
+ w:themeShade='7F'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h6).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading6")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading6'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 6'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading6Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='5'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='243F60'
+ w:themeColor='accent1'
+ w:themeShade='7F'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h7).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading7")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading7'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 7'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading7Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='6'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='404040'
+ w:themeColor='text1'
+ w:themeTint='BF'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h8).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading8")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading8'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 8'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading8Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='7'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:color w:val='404040'
+ w:themeColor='text1'
+ w:themeTint='BF'/>
+ <w:sz w:val='20'/>
+ <w:szCs w:val='20'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h9).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "Heading9")
+ .FirstOrDefault(),
+@"<w:style w:type='paragraph'
+ w:styleId='Heading9'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='heading 9'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:link w:val='Heading9Char'/>
+ <w:uiPriority w:val='9'/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:keepNext/>
+ <w:keepLines/>
+ <w:spacing w:before='200'
+ w:after='0'/>
+ <w:outlineLvl w:val='8'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='404040'
+ w:themeColor='text1'
+ w:themeTint='BF'/>
+ <w:sz w:val='20'/>
+ <w:szCs w:val='20'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h1).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading1Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading1Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 1 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading1'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:color w:val='365F91'
+ w:themeColor='accent1'
+ w:themeShade='BF'/>
+ <w:sz w:val='28'/>
+ <w:szCs w:val='28'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h2).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading2Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading2Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 2 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading2'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:color w:val='4F81BD'
+ w:themeColor='accent1'/>
+ <w:sz w:val='26'/>
+ <w:szCs w:val='26'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h3).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading3Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading3Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 3 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading3'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:color w:val='4F81BD'
+ w:themeColor='accent1'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h4).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading4Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading4Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 4 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading4'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='4F81BD'
+ w:themeColor='accent1'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h5).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading5Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading5Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 5 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading5'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:color w:val='243F60'
+ w:themeColor='accent1'
+ w:themeShade='7F'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h6).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading6Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading6Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 6 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading6'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='243F60'
+ w:themeColor='accent1'
+ w:themeShade='7F'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h7).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading7Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading7Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 7 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading7'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='404040'
+ w:themeColor='text1'
+ w:themeTint='BF'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h8).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading8Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading8Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 8 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading8'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:color w:val='404040'
+ w:themeColor='text1'
+ w:themeTint='BF'/>
+ <w:sz w:val='20'/>
+ <w:szCs w:val='20'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.h9).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Heading9Char")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:customStyle='1'
+ w:styleId='Heading9Char'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Heading 9 Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='Heading9'/>
+ <w:uiPriority w:val='9'/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:i/>
+ <w:iCs/>
+ <w:color w:val='404040'
+ w:themeColor='text1'
+ w:themeTint='BF'/>
+ <w:sz w:val='20'/>
+ <w:szCs w:val='20'/>
+ </w:rPr>
+</w:style>");
+
+ if (html.Descendants(XhtmlNoNamespace.a).Any())
+ PtUtils.AddElementIfMissing(styleXDoc,
+ styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Hyperlink")
+ .FirstOrDefault(),
+@"<w:style w:type='character'
+ w:styleId='Hyperlink'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Hyperlink' />
+ <w:basedOn w:val='DefaultParagraphFont' />
+ <w:uiPriority w:val='99' />
+ <w:semiHidden />
+ <w:unhideWhenUsed />
+ <w:rPr>
+ <w:color w:val='0000FF' />
+ <w:u w:val='single' />
+ </w:rPr>
+</w:style>");
+
+ wDoc.MainDocumentPart.StyleDefinitionsPart.PutXDocument();
+ }
+ }
+
+ class ThemeUpdater
+ {
+ public static void UpdateThemePart(WordprocessingDocument wDoc, XElement html, HtmlToWmlConverterSettings settings)
+ {
+ XDocument themeXDoc = wDoc.MainDocumentPart.ThemePart.GetXDocument();
+
+ CssExpression minorFont = html.Descendants(XhtmlNoNamespace.body).FirstOrDefault().GetProp("font-family");
+ XElement majorFontElement = html.Descendants().Where(e =>
+ e.Name == XhtmlNoNamespace.h1 ||
+ e.Name == XhtmlNoNamespace.h2 ||
+ e.Name == XhtmlNoNamespace.h3 ||
+ e.Name == XhtmlNoNamespace.h4 ||
+ e.Name == XhtmlNoNamespace.h5 ||
+ e.Name == XhtmlNoNamespace.h6 ||
+ e.Name == XhtmlNoNamespace.h7 ||
+ e.Name == XhtmlNoNamespace.h8 ||
+ e.Name == XhtmlNoNamespace.h9).FirstOrDefault();
+ CssExpression majorFont = null;
+ if (majorFontElement != null)
+ majorFont = majorFontElement.GetProp("font-family");
+
+ XAttribute majorTypeface = themeXDoc
+ .Root
+ .Elements(A.themeElements)
+ .Elements(A.fontScheme)
+ .Elements(A.majorFont)
+ .Elements(A.latin)
+ .Attributes(NoNamespace.typeface)
+ .FirstOrDefault();
+ if (majorTypeface != null && majorFont != null)
+ {
+ CssTerm term = majorFont.Terms.FirstOrDefault();
+ if (term != null)
+ majorTypeface.Value = term.Value;
+ }
+ XAttribute minorTypeface = themeXDoc
+ .Root
+ .Elements(A.themeElements)
+ .Elements(A.fontScheme)
+ .Elements(A.minorFont)
+ .Elements(A.latin)
+ .Attributes(NoNamespace.typeface)
+ .FirstOrDefault();
+ if (minorTypeface != null && minorFont != null)
+ {
+ CssTerm term = minorFont.Terms.FirstOrDefault();
+ if (term != null)
+ minorTypeface.Value = term.Value;
+ }
+
+ wDoc.MainDocumentPart.ThemePart.PutXDocument();
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/HtmlToWmlCssApplier.cs b/OpenXmlPowerTools/HtmlToWmlCssApplier.cs
new file mode 100644
index 0000000..5bbb87c
--- /dev/null
+++ b/OpenXmlPowerTools/HtmlToWmlCssApplier.cs
@@ -0,0 +1,3758 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using OpenXmlPowerTools;
+using OpenXmlPowerTools.HtmlToWml;
+using OpenXmlPowerTools.HtmlToWml.CSS;
+using System.Globalization;
+
+#if false
+Sort:
+1. User agent declarations
+ 1. Default value
+ 2. Specified in default style sheet (which I am going to supply)
+2. User normal declarations
+ 1. Specified in user-supplied style sheet
+3. Author normal declarations
+ 1. Specified in style sheet (can defer this until later)
+ 2. Specified in style element
+ 3. Specified in style attribute
+4. Author important declarations
+ 1. Specified in style sheet
+5. User important declarations
+ 1. Specified in style sheet
+
+Sort Key (most significant first)
+{0} 9 if user supplied style sheet, high
+{0} 8 if author supplied style sheet, high
+{0} 7 if style attribute, high
+{0} 6 if style attribute, normal
+{0} 5 if author suplied style sheet, normal
+{0} 4 if user supplied style sheet, normal
+{0} 3 if user-agent default style sheet, high
+{0} 2 if user-agent default style sheet, normal
+{0} 1 if inherited
+{1} count of number of ID attributes in selector
+{2} count of number of attributes and pseudo classes in selector
+{3} count of number of element names and pseudo elements in selector
+{4} sequence number in order added to list
+
+1. Process user-agent default style sheet
+2. Process user-supplied style sheet
+3. process author-supplied style sheet
+4. process STYLE element
+5. process style attribute on all elements
+6. expand all shorthand properties - the new properties have the same sort key as shorthand prop
+7. Add initial values for all properties that don't have values
+
+The various types of properties:
+- Set by the style sheet explicitely
+- Set at root level of hierarchy so that other elements can inherit (if no other ancestor)
+- Set as initial value for certain elements - all properties have an initial value. Some properties apply to only certain elements.
+- Some properties inherit automatically, such as font-family.
+- Some properties are set to inherit in the stylesheet.
+- border-top-color etc. are set to the color property, which then inherits.
+
+Properties either inherit, or are set to an initial value. Those properties that inherit are set to initial value only at the top of
+the tree. Those props that are set to an initial value throughout the tree do not inherit. It is either one or the other.
+
+Following is my new theory of the correct algorithm:
+- set all properties from the cascade.
+- Set initial values for properties at the top of the tree. These specifically need to be set for other properties that will
+ inherit from them.
+- create a matrix of all elements, and which properties need to be set for each element.
+- iterate through the tree.
+
+ SetAllValues
+ for each element
+ for each property
+ call GetComputedPropertyValue // implemented in a recursive fashion to set & get its computed value.
+
+ GetComputedPropertyValue
+ if (property is already computed)
+ return the computed value
+ if (property is set)
+ compute value
+ set the computed value
+ return the computed value
+ if (property is inherited (either because it was set to inherit, or because it is an inherited property))
+ if (parent exists)
+ call GetComputedValue on parent
+ set the computed value
+ return the computed value
+ else
+ call GetInitialValue for property
+ compute value
+ set the computed value
+ return the computed value
+ else
+ call GetInitialValue for property
+ compute value
+ set the computed value
+ return the computed value
+
+ ComputeValue
+ this needs to be specifically coded for each property
+ if value is relative (em, ex, percentage,
+ if property is not on font-size
+ GetComputedValue for font-size
+ compute value accordingly
+ return value
+ if property is on font-size
+ call GetComputedValue for font-size of parent
+ compute value accordingly
+ return value
+ if value is percentage
+ if margin-top, margin-bottom, margin-right, margin-left, padding-top, padding-bottom, padding-right, padding-left
+ call GetComputedValue for width of containing block
+ if value is absolute (in, cm, mm, pt, pc, px)
+ return value
+#endif
+
+namespace OpenXmlPowerTools.HtmlToWml
+{
+ class CssApplier
+ {
+ private static List<PropertyInfo> PropertyInfoList = new List<PropertyInfo>()
+ {
+ // color
+ // Value: <color> | inherit
+ // Initial: depends on UA
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "color" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "black", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = (element, assignedValue, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = GetWmlColorFromExpression(assignedValue), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ },
+
+ // direction
+ // Value: ltr | rtl | inherit
+ // Initial: ltr
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "direction" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "ltr", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // line-height
+ // Value: normal | <number> | <length> | <percentage> | <inherit>
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: refer to the font size of the element itself
+ // Computed value: for <length> and <percentage> the absolute value, otherwise as specified.
+ new PropertyInfo
+ {
+ Names = new[] { "line-height" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element, "font-size", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // visibility
+ // Value: visible | hidden | collapse | inherit
+ // Initial: visible
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "visibility" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "visible", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // list-style-type
+ // Value: disc | circle | square | decimal | decimal-leading-zero |
+ // lower-roman | upper-roman | lower-greek | lower-latin |
+ // upper-latin | armenian | georgian | lower-alpha | upper-alpha |
+ // none | inherit
+ // Initial: disc
+ // Applies to: elements with display: list-item
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "list-style-type" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "list-item")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "disc", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // list-style-image
+ // Value: <uri> | none | inherit
+ // Initial: none
+ // Applies to: elements with ’display: list-item’
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: absolute URI or ’none’
+ new PropertyInfo
+ {
+ Names = new[] { "list-style-image" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "list-item")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // list-style-position
+ // Value: inside | outside | inherit
+ // Initial: outside
+ // Applies to: elements with ’display: list-item’
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "list-style-position" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "list-item")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // font-family
+ // Value: [[ <family-name> | <generic-family> ] [, <family-name>|
+ // <generic-family>]* ] | inherit
+ // Initial: depends on user agent
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "font-family" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = settings.MinorLatinFont, Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = (element, assignedValue, settings) => assignedValue,
+ },
+
+ // font-style
+ // Value: normal | italic | oblique | inherit
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "font-style" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // font-variant
+ // Value: normal | small-caps | inherit
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "font-variant" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // font-weight
+ // Value: normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 |
+ // 600 | 700 | 800 | 900 | inherit
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: see text
+ new PropertyInfo
+ {
+ Names = new[] { "font-weight" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // font-size
+ // Value: <absolute-size> | <relative-size> | <length> | <percentage> |
+ // inherit
+ // Initial: medium
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: refer to inherited font size
+ // Computed value: absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "font-size" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = ((double)settings.DefaultFontSize).ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, Unit = CssUnit.PT } } },
+ ComputedValue = (element, assignedValue, settings) => ComputeAbsoluteFontSize(element, assignedValue, settings),
+ },
+
+ // text-indent
+ // Value: <length> | <percentage> | inherit
+ // Initial: 0
+ // Applies to: block containers
+ // Inherited: yes
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage as specified or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "text-indent" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "block")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "width", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // text-align
+ // Value: left | right | center | justify | inherit
+ // Initial: a nameless value that acts as ’left’ if ’direction’ is ’ltr’, ’right’ if
+ // ’direction’ is ’rtl’
+ // Applies to: block containers
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: the initial value or as spec
+ new PropertyInfo
+ {
+ Names = new[] { "text-align" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "block")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "left", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } }, // todo should be based on the direction property
+ ComputedValue = null,
+ },
+
+ // text-decoration
+ // Value: none | [ underline || overline || line-through || blink ] | inherit
+ // Initial: none
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "text-decoration" },
+ Inherits = true, // todo need to read css 16.3.1 in full detail to understand how this is implemented.
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = null,
+ },
+
+ // letter-spacing
+ // Value: normal | <length> | inherit
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: ’normal’ or absolute length
+
+ // word-spacing
+ // Value: normal | <length> | inherit
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: for ’normal’ the value 0; otherwise the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "letter-spacing", "word-spacing" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = (element, assignedValue, settings) => ComputeAbsoluteLength(element, assignedValue, settings, null),
+ },
+
+ // white-space
+ // Value: normal | pre | nowrap | pre-wrap | pre-line | inherit
+ // Initial: normal
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "white-space" },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = null,
+ },
+
+ // caption-side
+ // Value: top | bottom | inherit
+ // Initial: top
+ // Applies to: 'table-caption' elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "caption-side" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table-caption")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "top", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = null,
+ },
+
+ // border-collapse
+ // Value: collapse | separate | inherit
+ // Initial: separate
+ // Applies to: ’table’ and ’inline-table’ elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "border-collapse" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table" || display.ToString() == "inline-table")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "separate", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = null,
+ },
+
+ // border-spacing
+ // Value: <length> <length>? | inherit
+ // Initial: 0
+ // Applies to: ’table’ and ’inline-table’ elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: two absolute lengths
+ new PropertyInfo
+ {
+ Names = new[] { "border-spacing" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table" || display.ToString() == "inline-table")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) => ComputeAbsoluteLength(element, assignedValue, settings, null), // todo need to handle two lengths here
+ },
+
+ // empty-cells
+ // Value: show | hide | inherit
+ // Initial: show
+ // Applies to: 'table-cell' elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "empty-cells" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table" || display.ToString() == "table-cell")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "show", } } },
+ ComputedValue = null,
+ },
+
+ // margin-top, margin-bottom
+ // Value: <margin-width> | inherit
+ // Initial: 0
+ // Applies to: all elements except elements with table display types other than table-caption, table, and inline-table
+ // all elements except th, td, tr
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage as specified or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "margin-top", "margin-bottom", },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table-caption" || display.ToString() == "table" || display.ToString() == "inline-table")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) =>
+ {
+ if (settings.DefaultBlockContentMargin != null)
+ {
+ if (settings.DefaultBlockContentMargin == "auto")
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "auto", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } };
+ else if (settings.DefaultBlockContentMargin.ToLower().EndsWith("pt"))
+ {
+ string s1 = settings.DefaultBlockContentMargin.Substring(0, settings.DefaultBlockContentMargin.Length - 2);
+ double d1;
+ if (double.TryParse(s1, NumberStyles.Float, CultureInfo.InvariantCulture, out d1))
+ {
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = d1.ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT } } };
+ }
+ }
+ throw new OpenXmlPowerToolsException("invalid setting");
+ }
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT } } };
+ },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "width", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // margin-right, margin-left
+ // Value: <margin-width> | inherit
+ // Initial: 0
+ // Applies to: all elements except elements with table display types other than table-caption, table, and inline-table
+ // all elements except th, td, tr
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage as specified or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "margin-right", "margin-left", },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table-caption" || display.ToString() == "table" || display.ToString() == "inline-table")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "width", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // padding-top, padding-right, padding-bottom, padding-left
+ // Value: <padding-width> | inherit
+ // Initial: 0
+ // Applies to: all elements except table-row-group, table-header-group,
+ // table-footer-group, table-row, table-column-group and table-column
+ // all elements except tr
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage as specified or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "padding-top", "padding-right", "padding-bottom", "padding-left" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ string dv = display.ToString();
+ if (dv == "table-row-group" || dv == "table-header-group" || dv == "table-footer-group" || dv == "table-row" ||
+ dv == "table-column-group" || dv == "table-column")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "width", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // border-top-width, border-right-width, border-bottom-width, border-left-width
+ // Value: <border-width> | inherit
+ // Initial: medium
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: absolute length; '0' if the border style is 'none' or 'hidden'
+ new PropertyInfo
+ {
+ Names = new[] { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ string assignedValueStr = assignedValue.ToString();
+ if (assignedValueStr == "thin")
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0.75", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ if (assignedValueStr == "medium")
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "3.0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ if (assignedValueStr == "thick")
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "4.5", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ return ComputeAbsoluteLength(element, assignedValue, settings, null);
+ },
+ },
+
+ // border-top-style, border-right-style, border-bottom-style, border-left-style
+ // Value: <border-style> | inherit
+ // Initial: none
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as specified
+ new PropertyInfo
+ {
+ Names = new[] { "border-top-style", "border-right-style", "border-bottom-style", "border-left-style", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // display
+ // Value: inline | block | list-item | inline-block | table | inline-table |
+ // table-row-group | table-header-group | table-footer-group |
+ // table-row | table-column-group | table-column | table-cell |
+ // table-caption | none | inherit
+ // Initial: inline
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: see text
+ new PropertyInfo
+ {
+ Names = new[] { "display", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inline", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // position
+ // Value: static | relative | absolute | fixed | inherit
+ // Initial: static
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as specified
+ new PropertyInfo
+ {
+ Names = new[] { "position", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "static", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // float
+ // Value: left | right | none | inherit
+ // Initial: none
+ // Applies to: all, but see 9.7 p. 153
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as specified
+ new PropertyInfo
+ {
+ Names = new[] { "float", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // unicode-bidi
+ // Value: normal | embed | bidi-override | inherit
+ // Initial: normal
+ // Applies to: all elements, but see prose
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "unicode-bidi", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // background-color
+ // Value: <color> | transparent | inherit
+ // Initial: transparent
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "background-color", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "transparent", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = (element, assignedValue, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = GetWmlColorFromExpression(assignedValue), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ },
+
+ // text-transform
+ // Value: capitalize | uppercase | lowercase | none | inherit
+ // Initial: none
+ // Applies to: all elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "text-transform", },
+ Inherits = true,
+ Includes = (e, settings) => true,
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ ComputedValue = null,
+ },
+
+ // table-layout
+ // Value: auto | fixed | inherit
+ // Initial: auto
+ // Applies to: ’table’ and ’inline-table’ elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "table-layout" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table" || display.ToString() == "inline-table")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "auto", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = null,
+ },
+
+ // empty-cells
+ // Value: show | hide | inherit
+ // Initial: show
+ // Applies to: 'table-cell' elements
+ // Inherited: yes
+ // Percentages: N/A
+ // Computed value: as spec
+ new PropertyInfo
+ {
+ Names = new[] { "border-spacing" },
+ Inherits = true,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ if (display.ToString() == "table-cell")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "show", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = null,
+ },
+
+ // border-top-color, border-right-color, border-bottom-color, border-left-color
+ // Value: <color> | transparent | inherit
+ // Initial: the value of the color property
+ // Applies to: all elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: when taken from the ’color’ property, the computed value of
+ // ’color’; otherwise, as specified
+ new PropertyInfo
+ {
+ Names = new[] { "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", },
+ Inherits = false,
+ Includes = (e, settings) => true,
+ InitialValue = (e, settings) => {
+ var display = GetComputedPropertyValue(null, e, "color", settings);
+ return display;
+ },
+ ComputedValue = (element, assignedValue, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = GetWmlColorFromExpression(assignedValue), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } },
+ },
+
+ // width
+ // Value: <length> | <percentage> | auto | inherit
+ // Initial: auto
+ // Applies to: all elements but non-replaced in-line elements, table rows, and row groups
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage or 'auto' as specified or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "width" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ if (e.Name == XhtmlNoNamespace.img)
+ return true;
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-row" || dv == "table-row-group")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) =>
+ {
+ if (element.Parent == null)
+ {
+ double? pageWidth = (double?)settings.SectPr.Elements(W.pgSz).Attributes(W._w).FirstOrDefault();
+ if (pageWidth == null)
+ pageWidth = 12240;
+ double? leftMargin = (double?)settings.SectPr.Elements(W.pgMar).Attributes(W.left).FirstOrDefault();
+ if (leftMargin == null)
+ leftMargin = 1440;
+ double? rightMargin = (double?)settings.SectPr.Elements(W.pgMar).Attributes(W.left).FirstOrDefault();
+ if (rightMargin == null)
+ rightMargin = 1440;
+ double width = (double)(pageWidth - leftMargin - rightMargin) / 20;
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = width.ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, Unit = CssUnit.PT, } } };
+ }
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "auto", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } };
+ },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ if (element.Name != XhtmlNoNamespace.caption &&
+ element.Name != XhtmlNoNamespace.td &&
+ element.Name != XhtmlNoNamespace.th &&
+ element.Name != XhtmlNoNamespace.tr &&
+ element.Name != XhtmlNoNamespace.table &&
+ assignedValue.IsAuto)
+ {
+ PropertyInfo pi = PropertyInfoList.FirstOrDefault(p => p.Names.Contains("width"));
+ string display = GetComputedPropertyValue(pi, element, "display", settings).ToString();
+ if (display != "inline")
+ {
+ CssExpression parentPropertyValue = GetComputedPropertyValue(pi, element.Parent, "width", settings);
+ return parentPropertyValue;
+ }
+ }
+ CssExpression valueForPercentage = null;
+ XElement elementToQuery = element.Parent;
+ while (elementToQuery != null)
+ {
+ valueForPercentage = GetComputedPropertyValue(null, elementToQuery, "width", settings);
+ if (valueForPercentage.IsAuto)
+ {
+ elementToQuery = elementToQuery.Parent;
+ continue;
+ }
+ break;
+ }
+
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // min-width
+ // Value: <length> | <percentage> | inherit
+ // Initial: 0
+ // Applies to: all elements but non-replaced in-line elements, table rows, and row groups
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage as spec or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "min-width" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-row" || dv == "table-row-group")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "width", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // max-width
+ // Value: <length> | <percentage> | none | inherit
+ // Initial: none
+ // Applies to: all elements but non-replaced in-line elements, table rows, and row groups
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: the percentage as spec or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "max-width" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-row" || dv == "table-row-group")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "width", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // height
+ // Value: <length> | <percentage> | auto | inherit
+ // Initial: auto
+ // Applies to: all elements but non-replaced in-line elements, table columns, and column groups
+ // Inherited: no
+ // Percentages: see prose
+ // Computed value: the percentage as spec or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "height" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ if (e.Name == XhtmlNoNamespace.img)
+ return true;
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-row" || dv == "table-row-group")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "auto", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "height", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // min-height
+ // Value: <length> | <percentage> | inherit
+ // Initial: 0
+ // Applies to: all elements but non-replaced in-line elements, table columns, and column groups
+ // Inherited: no
+ // Percentages: see prose
+ // Computed value: the percentage as spec or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "min-height" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-column" || dv == "table-column-group")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "0", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "height", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // max-height
+ // Value: <length> | <percentage> | none | inherit
+ // Initial: none
+ // Applies to: all elements but non-replaced in-line elements, table columns, and column groups
+ // Inherited: no
+ // Percentages: refer to height of containing block
+ // Computed value: the percentage as spec or the absolute length
+ new PropertyInfo
+ {
+ Names = new[] { "max-height" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-column" || dv == "table-column-group")
+ return false;
+ return true;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = (element, assignedValue, settings) =>
+ {
+ CssExpression valueForPercentage = null;
+ if (element.Parent != null)
+ valueForPercentage = GetComputedPropertyValue(null, element.Parent, "height", settings);
+ return ComputeAbsoluteLength(element, assignedValue, settings, valueForPercentage);
+ },
+ },
+
+ // vertical-align
+ // Value: baseline | sub | super | top | text-top | middle | bottom | text-bottom |
+ // <percentage> | <length> | inherit
+ // Initial: baseline
+ // Applies to: inline-level and 'table-cell' elements
+ // Inherited: no
+ // Percentages: refer to the line height of the element itself
+ // Computed value: for <length> and <percentage> the absolute length, otherwise as specified.
+ new PropertyInfo
+ {
+ Names = new[] { "vertical-align" },
+ Inherits = false,
+ Includes = (e, settings) =>
+ {
+ var display = GetComputedPropertyValue(null, e, "display", settings);
+ var dv = display.ToString();
+ if (dv == "inline" || dv == "table-cell")
+ return true;
+ return false;
+ },
+ InitialValue = (element, settings) => new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "baseline", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String, } } },
+ ComputedValue = (element, assignedValue, settings) => assignedValue, // todo fix
+ },
+
+ // positioned elements are not supported
+ //
+ // top
+ // Value: <length> | <percentage> | auto | inherit
+ // Initial: auto
+ // Applies to: positioned elements
+ // Inherited: no
+ // Percentages: refer to height of containing block
+ // Computed value: if specified as a length, the corresponding absolute length; if
+ // specified as a percentage, the specified value; otherwise, ’auto’.
+ //
+ // right
+ // Value: <length> | <percentage> | auto | inherit
+ // Initial: auto
+ // Applies to: positioned elements
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: if specified as a length, the corresponding absolute length; if
+ // specified as a percentage, the specified value; otherwise, ’auto’.
+ //
+ // bottom
+ // Value: <length> | <percentage> | auto | inherit
+ // Initial: auto
+ // Applies to: positioned elements
+ // Inherited: no
+ // Percentages: refer to height of containing block
+ // Computed value: if specified as a length, the corresponding absolute length; if
+ // specified as a percentage, the specified value; otherwise, ’auto’.
+ //
+ // left
+ // Value: <length> | <percentage> | auto | inherit
+ // Initial: auto
+ // Applies to: positioned elements
+ // Inherited: no
+ // Percentages: refer to width of containing block
+ // Computed value: if specified as a length, the corresponding absolute length; if
+ // specified as a percentage, the specified value; otherwise, ’auto’.
+
+ // floated elements are not supported
+ //
+ // clear
+ // Value: none | left | right | both | inherit
+ // Initial: none
+ // Applies to: block-level elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as specified
+ //
+ // z-index
+ // Value: auto | integer | inherit
+ // Initial: auto
+ // Applies to: positioned elements
+ // Inherited: no
+ // Percentages: N/A
+ // Computed value: as spec
+ };
+
+ /*
+ * 1. Process user-agent default style sheet
+ * 2. Process user-supplied style sheet
+ * 3. process author-supplied style sheet
+ * 4. process STYLE element
+ * 5. process style attribute on all elements
+ * 6. expand all shorthand properties - the new properties have the same sort key as shorthand prop
+ * 7. Add initial values for all properties that don't have values
+ */
+
+ public static void ApplyAllCss(
+ string defaultCss,
+ string authorCss,
+ string userCss,
+ XElement newXHtml,
+ HtmlToWmlConverterSettings settings,
+ out CssDocument defaultCssDoc,
+ out CssDocument authorCssDoc,
+ out CssDocument userCssDoc,
+ string annotatedHtmlDumpFileName)
+ {
+ int propertySequence = 1;
+
+ CssParser defaultCssParser = new CssParser();
+ defaultCssDoc = defaultCssParser.ParseText(defaultCss);
+ ApplyCssDocument(
+ defaultCssDoc,
+ newXHtml,
+ Property.HighOrderPriority.UserAgentNormal,
+ Property.HighOrderPriority.UserAgentHigh,
+ ref propertySequence);
+
+ //// todo dump here, see if margin is set on body.
+ //if (annotatedHtmlDumpFileName != null)
+ //{
+ // StringBuilder sb = new StringBuilder();
+ // WriteXHtmlWithAnnotations(newXHtml, sb);
+ // File.WriteAllText(annotatedHtmlDumpFileName, sb.ToString());
+ // Environment.Exit(0);
+ //}
+
+ //DumpCss(userAgentCssDoc);
+ //Environment.Exit(0);
+
+ CssParser userCssParser = new CssParser();
+ userCssDoc = userCssParser.ParseText(userCss);
+ ApplyCssDocument(
+ userCssDoc,
+ newXHtml,
+ Property.HighOrderPriority.UserHigh,
+ Property.HighOrderPriority.UserNormal,
+ ref propertySequence);
+
+ //DumpCss(userCssDoc);
+ //Environment.Exit(0);
+
+ CssParser authorCssParser = new CssParser();
+ authorCssDoc = authorCssParser.ParseText(authorCss);
+ ApplyCssDocument(
+ authorCssDoc,
+ newXHtml,
+ Property.HighOrderPriority.AuthorNormal,
+ Property.HighOrderPriority.AuthorHigh,
+ ref propertySequence);
+
+ //string s = DumpCss(authorCssDoc);
+ //File.WriteAllText("CssTreeDump.txt", s);
+ //Environment.Exit(0);
+
+ // If processing style element, do it here.
+
+ ApplyStyleAttributes(newXHtml, ref propertySequence);
+
+ ExpandShorthandProperties(newXHtml, settings);
+
+ SetAllValues(newXHtml, settings);
+
+ if (annotatedHtmlDumpFileName != null)
+ {
+ StringBuilder sb = new StringBuilder();
+ WriteXHtmlWithAnnotations(newXHtml, sb);
+ File.WriteAllText(annotatedHtmlDumpFileName, sb.ToString());
+ }
+ }
+
+ private static void SetAllValues(XElement xHtml, HtmlToWmlConverterSettings settings)
+ {
+ foreach (var element in xHtml.DescendantsAndSelf())
+ {
+ foreach (var propertyInfo in PropertyInfoList)
+ {
+ if (propertyInfo.Includes(element, settings))
+ {
+ foreach (var name in propertyInfo.Names)
+ {
+ GetComputedPropertyValue(propertyInfo, element, name, settings);
+ }
+ }
+ }
+ }
+ }
+
+#if false
+ GetComputedPropertyValue
+ if (property is already computed)
+ return the computed value
+ if (property is set)
+ compute value
+ set the computed value
+ return the computed value
+ if (property is inherited (either because it was set to inherit, or because it is an inherited property))
+ if (parent exists)
+ call GetComputedValue on parent
+ return the computed value
+ else
+ call GetInitialValue for property
+ compute value
+ set the computed value
+ return the computed value
+#endif
+ public static CssExpression GetComputedPropertyValue(PropertyInfo propertyInfo, XElement element, string propertyName,
+ HtmlToWmlConverterSettings settings)
+ {
+ // if (property is already computed)
+ // return the computed value
+ Dictionary<string, CssExpression> computedValues = element.Annotation<Dictionary<string, CssExpression>>();
+ if (computedValues == null)
+ {
+ computedValues = new Dictionary<string, CssExpression>();
+ element.AddAnnotation(computedValues);
+ }
+ if (computedValues.ContainsKey(propertyName))
+ {
+ CssExpression r = computedValues[propertyName];
+ return r;
+ }
+
+ // if property is not set or property is set to inherited value, then get inherited or initialized value.
+ string pName = propertyName.ToLower();
+ if (propertyInfo == null)
+ {
+ propertyInfo = PropertyInfoList.FirstOrDefault(pi => pi.Names.Contains(pName));
+ if (propertyInfo == null)
+ throw new OpenXmlPowerToolsException("all possible properties should be in the list");
+ }
+ Dictionary<string, Property> propList = element.Annotation<Dictionary<string, Property>>();
+ if (propList == null)
+ {
+ CssExpression computedValue = GetInheritedOrInitializedValue(computedValues, propertyInfo, element, propertyName, false, settings);
+ return computedValue;
+ }
+ if (!propList.ContainsKey(pName))
+ {
+ CssExpression computedValue = GetInheritedOrInitializedValue(computedValues, propertyInfo, element, propertyName, false, settings);
+ return computedValue;
+ }
+ Property prop = propList[pName];
+ string propStr = prop.Expression.ToString();
+ if (propStr == "inherited" || propStr == "auto")
+ {
+ CssExpression computedValue = GetInheritedOrInitializedValue(computedValues, propertyInfo, element, propertyName, true, settings);
+ return computedValue;
+ }
+ // if property is set, then compute the value, return the computed value
+ CssExpression computedValue2;
+ if (propertyInfo.ComputedValue == null)
+ computedValue2 = prop.Expression;
+ else
+ {
+ computedValue2 = propertyInfo.ComputedValue(element, prop.Expression, settings);
+ }
+ computedValues.Add(propertyName, computedValue2);
+ return computedValue2;
+ }
+
+ //if (property is inherited (either because it was set to inherit, or because it is an inherited property))
+ // if (parent exists)
+ // call GetComputedValue on parent
+ // return the computed value
+ //else
+ // call GetInitialValue for property
+ // compute value
+ // set the computed value
+ // return the computed value
+ public static CssExpression GetInheritedOrInitializedValue(Dictionary<string, CssExpression> computedValues, PropertyInfo propertyInfo, XElement element, string propertyName, bool valueIsInherit, HtmlToWmlConverterSettings settings)
+ {
+ if ((propertyInfo.Inherits || valueIsInherit) && element.Parent != null && propertyInfo.Includes(element.Parent, settings))
+ {
+ CssExpression parentPropertyValue = GetComputedPropertyValue(propertyInfo, element.Parent, propertyName, settings);
+ computedValues.Add(propertyName, parentPropertyValue);
+ return parentPropertyValue;
+ }
+ CssExpression initialPropertyValue = propertyInfo.InitialValue(element, settings);
+ CssExpression computedValue;
+ if (propertyInfo.ComputedValue == null)
+ computedValue = initialPropertyValue;
+ else
+ computedValue = propertyInfo.ComputedValue(element, initialPropertyValue, settings);
+ computedValues.Add(propertyName, computedValue);
+ return computedValue;
+ }
+
+ private static void ApplyCssDocument(
+ CssDocument cssDoc,
+ XElement xHtml,
+ Property.HighOrderPriority notImportantHighOrderSort,
+ Property.HighOrderPriority importantHighOrderSort,
+ ref int propertySequence)
+ {
+ foreach (var ruleSet in cssDoc.RuleSets)
+ {
+ foreach (var selector in ruleSet.Selectors)
+ {
+ ApplySelector(selector, ruleSet, xHtml, notImportantHighOrderSort,
+ importantHighOrderSort, ref propertySequence);
+ }
+ }
+ }
+
+ private static CssExpression ComputeAbsoluteLength(XElement element, CssExpression assignedValue, HtmlToWmlConverterSettings settings,
+ CssExpression lengthForPercentage)
+ {
+ if (assignedValue.Terms.Count != 1)
+ throw new OpenXmlPowerToolsException("Should not have a unit with more than one term");
+
+ string value = assignedValue.Terms.First().Value;
+ bool negative = assignedValue.Terms.First().Sign == '-';
+
+ if (value == "thin")
+ {
+ CssExpression newExpr1 = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = ".3", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ return newExpr1;
+ }
+ if (value == "medium")
+ {
+ CssExpression newExpr2 = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "1.20", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ return newExpr2;
+ }
+ if (value == "thick")
+ {
+ CssExpression newExpr3 = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "1.80", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ return newExpr3;
+ }
+ if (value == "auto" || value == "normal" || value == "none")
+ return assignedValue;
+
+ CssUnit? unit = assignedValue.Terms.First().Unit;
+ if (unit == CssUnit.PT || unit == null)
+ return assignedValue;
+
+ if (unit == CssUnit.Percent && lengthForPercentage == null)
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "auto", Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.String } } };
+
+ double decValue;
+ if (!double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue))
+ throw new OpenXmlPowerToolsException("value did not parse");
+ if (negative)
+ decValue = -decValue;
+
+ double? newPtSize = null;
+ if (unit == CssUnit.Percent)
+ {
+ double ptSize;
+ if (!double.TryParse(lengthForPercentage.Terms.First().Value, NumberStyles.Float, CultureInfo.InvariantCulture, out ptSize))
+ throw new OpenXmlPowerToolsException("did not return a double?");
+ newPtSize = ptSize * decValue / 100d;
+ }
+ else if (unit == CssUnit.EM || unit == CssUnit.EX)
+ {
+ CssExpression fontSize = GetComputedPropertyValue(null, element, "font-size", settings);
+ double decFontSize;
+ if (!double.TryParse(fontSize.Terms.First().Value, NumberStyles.Float, CultureInfo.InvariantCulture, out decFontSize))
+ throw new OpenXmlPowerToolsException("Internal error");
+ newPtSize = (unit == CssUnit.EM) ? decFontSize * decValue : decFontSize * decValue / 2;
+ }
+ else
+ {
+ if (unit == null && decValue == 0d)
+ newPtSize = 0d;
+ if (unit == CssUnit.IN)
+ newPtSize = decValue * 72.0d;
+ if (unit == CssUnit.CM)
+ newPtSize = (decValue / 2.54d) * 72.0d;
+ if (unit == CssUnit.MM)
+ newPtSize = (decValue / 25.4d) * 72.0d;
+ if (unit == CssUnit.PC)
+ newPtSize = decValue * 12d;
+ if (unit == CssUnit.PX)
+ newPtSize = decValue * 0.75d;
+ }
+ if (!newPtSize.HasValue)
+ throw new OpenXmlPowerToolsException("Internal error: should not have reached this exception");
+ CssExpression newExpr = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = newPtSize.Value.ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ return newExpr;
+ }
+
+ private static CssExpression ComputeAbsoluteFontSize(XElement element, CssExpression assignedValue, HtmlToWmlConverterSettings settings)
+ {
+ if (assignedValue.Terms.Count != 1)
+ throw new OpenXmlPowerToolsException("Should not have a unit with more than one term, I think");
+ string value = assignedValue.Terms.First().Value;
+ CssUnit? unit = assignedValue.Terms.First().Unit;
+ if (unit == CssUnit.PT)
+ return assignedValue;
+ if (FontSizeMap.ContainsKey(value))
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = FontSizeMap[value].ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+
+ // todo what should the calculation be for computing larger / smaller?
+ if (value == "larger" || value == "smaller")
+ {
+ CssExpression parentFontSize = GetComputedPropertyValue(null, element.Parent, "font-size", settings);
+ double ptSize;
+ if (!double.TryParse(parentFontSize.Terms.First().Value, NumberStyles.Float, CultureInfo.InvariantCulture, out ptSize))
+ throw new OpenXmlPowerToolsException("did not return a double?");
+ double newPtSize2 = 0;
+ if (value == "larger")
+ {
+ if (ptSize < 10)
+ newPtSize2 = 10d;
+ if (ptSize == 10 || ptSize == 11)
+ newPtSize2 = 12d;
+ if (ptSize == 12)
+ newPtSize2 = 13.5d;
+ if (ptSize >= 13 && ptSize <= 15)
+ newPtSize2 = 18d;
+ if (ptSize >= 16 && ptSize <= 20)
+ newPtSize2 = 24d;
+ if (ptSize >= 21)
+ newPtSize2 = 36d;
+ }
+ if (value == "smaller")
+ {
+ if (ptSize <= 11)
+ newPtSize2 = 7.5d;
+ if (ptSize == 12)
+ newPtSize2 = 10d;
+ if (ptSize >= 13 && ptSize <= 15)
+ newPtSize2 = 12d;
+ if (ptSize >= 16 && ptSize <= 20)
+ newPtSize2 = 13.5d;
+ if (ptSize >= 21 && ptSize <= 29)
+ newPtSize2 = 18d;
+ if (ptSize >= 30)
+ newPtSize2 = 24d;
+ }
+ return new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = newPtSize2.ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ }
+ double decValue;
+ if (!double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out decValue))
+ throw new OpenXmlPowerToolsException("em value did not parse");
+ double? newPtSize = null;
+ if (unit == CssUnit.EM || unit == CssUnit.EX || unit == CssUnit.Percent)
+ {
+ CssExpression parentFontSize = GetComputedPropertyValue(null, element.Parent, "font-size", settings);
+ double ptSize;
+ if (!double.TryParse(parentFontSize.Terms.First().Value, NumberStyles.Float, CultureInfo.InvariantCulture, out ptSize))
+ throw new OpenXmlPowerToolsException("did not return a double?");
+ if (unit == CssUnit.EM)
+ newPtSize = ptSize * decValue;
+ if (unit == CssUnit.EX)
+ newPtSize = ptSize / 2 * decValue;
+ if (unit == CssUnit.Percent)
+ newPtSize = ptSize * decValue / 100d;
+ }
+ else
+ {
+ if (unit == CssUnit.IN)
+ newPtSize = decValue * 72.0d;
+ if (unit == CssUnit.CM)
+ newPtSize = (decValue / 2.54d) * 72.0d;
+ if (unit == CssUnit.MM)
+ newPtSize = (decValue / 25.4d) * 72.0d;
+ if (unit == CssUnit.PC)
+ newPtSize = decValue * 12d;
+ if (unit == CssUnit.PX)
+ newPtSize = decValue * 0.75d;
+ }
+ if (!newPtSize.HasValue)
+ throw new OpenXmlPowerToolsException("Internal error: should not have reached this exception");
+ CssExpression newExpr = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = newPtSize.Value.ToString(CultureInfo.InvariantCulture), Type = OpenXmlPowerTools.HtmlToWml.CSS.CssTermType.Number, Unit = CssUnit.PT, } } };
+ return newExpr;
+ }
+
+ private static Dictionary<string, double> FontSizeMap = new Dictionary<string, double>()
+ {
+ { "xx-small", 7.5d },
+ { "x-small", 10d },
+ { "small", 12d },
+ { "medium", 13.5d },
+ { "large", 18d },
+ { "x-large", 24d },
+ { "xx-large", 36d },
+ };
+
+ private static void ApplySelector(
+ CssSelector selector,
+ CssRuleSet ruleSet,
+ XElement xHtml,
+ Property.HighOrderPriority notImportantHighOrderSort,
+ Property.HighOrderPriority importantHighOrderSort,
+ ref int propertySequence)
+ {
+ foreach (var element in xHtml.DescendantsAndSelf())
+ {
+ if (DoesSelectorMatch(selector, element))
+ {
+ foreach (CssDeclaration declaration in ruleSet.Declarations)
+ {
+ Property prop = new Property()
+ {
+ Name = declaration.Name.ToLower(),
+ Expression = declaration.Expression,
+ HighOrderSort = declaration.Important ? importantHighOrderSort : notImportantHighOrderSort,
+ IdAttributesInSelector = CountIdAttributesInSelector(selector),
+ AttributesInSelector = CountAttributesInSelector(selector),
+ ElementNamesInSelector = CountElementNamesInSelector(selector),
+ SequenceNumber = propertySequence++,
+ };
+ AddPropertyToElement(element, prop);
+ }
+ }
+ }
+ }
+
+ private static bool DoesSelectorMatch(
+ CssSelector selector,
+ XElement element)
+ {
+ int currentSimpleSelector = selector.SimpleSelectors.Count() - 1;
+ XElement currentElement = element;
+ while (true)
+ {
+ if (!DoesSimpleSelectorMatch(selector.SimpleSelectors[currentSimpleSelector], currentElement))
+ return false;
+ if (currentSimpleSelector == 0)
+ return true;
+ if (selector.SimpleSelectors[currentSimpleSelector].Combinator == CssCombinator.ChildOf)
+ {
+ currentElement = currentElement.Parent;
+ if (currentElement == null)
+ return false;
+ currentSimpleSelector--;
+ continue;
+ }
+ if (selector.SimpleSelectors[currentSimpleSelector].Combinator == CssCombinator.PrecededImmediatelyBy)
+ {
+ currentElement = currentElement.ElementsBeforeSelf().Reverse().FirstOrDefault();
+ if (currentElement == null)
+ return false;
+ currentSimpleSelector--;
+ continue;
+ }
+ if (selector.SimpleSelectors[currentSimpleSelector].Combinator == null)
+ {
+ bool continueOuter = false;
+ foreach (XElement ancestor in element.Ancestors())
+ {
+ if (DoesSimpleSelectorMatch(selector.SimpleSelectors[currentSimpleSelector - 1], ancestor))
+ {
+ currentElement = ancestor;
+ currentSimpleSelector--;
+ continueOuter = true;
+ break;
+ }
+ }
+ if (continueOuter)
+ continue;
+ return false;
+ }
+ }
+ }
+
+ private static bool DoesSimpleSelectorMatch(
+ CssSimpleSelector simpleSelector,
+ XElement element)
+ {
+ bool elemantNameMatch = true;
+ bool classNameMatch = true;
+ bool childSimpleSelectorMatch = true;
+ bool idMatch = true;
+ bool attributeMatch = true;
+
+ if (simpleSelector.Pseudo != null)
+ return false;
+ if (simpleSelector.ElementName != null && simpleSelector.ElementName != "" && simpleSelector.ElementName != "*")
+ elemantNameMatch = element.Name.ToString() == simpleSelector.ElementName.ToString();
+ if (elemantNameMatch)
+ {
+ if (simpleSelector.Class != null && simpleSelector.Class != "")
+ classNameMatch = ClassesOf(element).Contains(simpleSelector.Class);
+ if (classNameMatch)
+ {
+ if (simpleSelector.Child != null)
+ childSimpleSelectorMatch = DoesSimpleSelectorMatch(simpleSelector.Child, element);
+ if (childSimpleSelectorMatch)
+ {
+ if (simpleSelector.ID != null && simpleSelector.ID != "")
+ {
+ string id = (string)element.Attribute("ID");
+ if (id == null)
+ id = (string)element.Attribute("id");
+ idMatch = simpleSelector.ID == id;
+ }
+ if (idMatch)
+ {
+ if (simpleSelector.Attribute != null)
+ attributeMatch = DoesAttributeMatch(simpleSelector.Attribute, element);
+ }
+ }
+ }
+ }
+ bool result =
+ elemantNameMatch &&
+ classNameMatch &&
+ childSimpleSelectorMatch &&
+ idMatch &&
+ attributeMatch;
+ return result;
+ }
+
+ private static bool DoesAttributeMatch(OpenXmlPowerTools.HtmlToWml.CSS.CssAttribute attribute, XElement element)
+ {
+ string attName = attribute.Operand.ToLower();
+ string attValue = (string)element.Attribute(attName);
+ if (attValue == null)
+ return false;
+ if (attribute.Operator == null)
+ return true;
+ string value = attribute.Value;
+ switch (attribute.Operator)
+ {
+ case CssAttributeOperator.Equals:
+ return attValue == value;
+ case CssAttributeOperator.BeginsWith:
+ return attValue.StartsWith(value);
+ case CssAttributeOperator.Contains:
+ return attValue.Contains(value);
+ case CssAttributeOperator.EndsWith:
+ return attValue.EndsWith(value);
+ case CssAttributeOperator.InList:
+ return attValue.Split(' ').Contains(value);
+ case CssAttributeOperator.Hyphenated:
+ return attValue.Split('-')[0] == value;
+ default:
+ return false;
+ }
+ }
+
+ private static int CountIdAttributesInSimpleSelector(CssSimpleSelector simpleSelector)
+ {
+ int count = simpleSelector.ID != null ? 1 : 0 +
+ (simpleSelector.Child != null ? CountIdAttributesInSimpleSelector(simpleSelector.Child) : 0);
+ return count;
+ }
+
+ private static int CountIdAttributesInSelector(CssSelector selector)
+ {
+ int count = selector.SimpleSelectors.Select(ss => CountIdAttributesInSimpleSelector(ss)).Sum();
+ return count;
+ }
+
+ private static int CountAttributesInSimpleSelector(CssSimpleSelector simpleSelector)
+ {
+ int count = (simpleSelector.Attribute != null ? 1 : 0) +
+ ((simpleSelector.Class != null && simpleSelector.Class != "") ? 1 : 0) +
+ (simpleSelector.Child != null ? CountAttributesInSimpleSelector(simpleSelector.Child) : 0);
+ return count;
+ }
+
+ private static int CountAttributesInSelector(CssSelector selector)
+ {
+ int count = selector.SimpleSelectors.Select(ss => CountAttributesInSimpleSelector(ss)).Sum();
+ return count;
+ }
+
+ private static int CountElementNamesInSimpleSelector(CssSimpleSelector simpleSelector)
+ {
+ int count = (simpleSelector.ElementName != null &&
+ simpleSelector.ElementName != "" &&
+ simpleSelector.ElementName != "*")
+ ? 1 : 0 +
+ (simpleSelector.Child != null ? CountElementNamesInSimpleSelector(simpleSelector.Child) : 0);
+ return count;
+ }
+
+ private static int CountElementNamesInSelector(CssSelector selector)
+ {
+ int count = selector.SimpleSelectors.Select(ss => CountElementNamesInSimpleSelector(ss)).Sum();
+ return count;
+ }
+
+ private static void AddPropertyToElement(
+ XElement element,
+ Property property)
+ {
+ //if (property.Name == "direction")
+ // Console.WriteLine(1);
+ Dictionary<string, Property> propList = element.Annotation<Dictionary<string, Property>>();
+ if (propList == null)
+ {
+ propList = new Dictionary<string, Property>();
+ element.AddAnnotation(propList);
+ }
+ if (!propList.ContainsKey(property.Name))
+ propList.Add(property.Name, property);
+ else
+ {
+ Property current = propList[property.Name];
+ if (((System.IComparable<Property>)property).CompareTo(current) == 1)
+ propList[property.Name] = property;
+ }
+ }
+
+ private static void AddPropertyToDictionary(
+ Dictionary<string, Property> propList,
+ Property property)
+ {
+ if (!propList.ContainsKey(property.Name))
+ propList.Add(property.Name, property);
+ else
+ {
+ Property current = propList[property.Name];
+ if (((System.IComparable<Property>)property).CompareTo(current) == 1)
+ propList[property.Name] = property;
+ }
+ }
+
+ private static string[] ClassesOf(XElement element)
+ {
+ string classesString = (string)element.Attribute("class");
+ if (classesString == null)
+ return new string[0];
+ return classesString.Split(' ');
+ }
+
+ private static void ApplyDeclarationsToElement(
+ CssRuleSet ruleSet,
+ XElement element,
+ Property.HighOrderPriority notImportantHighOrderSort,
+ Property.HighOrderPriority importantHighOrderSort,
+ ref int propertySequence)
+ {
+ foreach (var declaration in ruleSet.Declarations)
+ {
+ Property prop = new Property()
+ {
+ Name = declaration.Name.ToLower(),
+ Expression = declaration.Expression,
+ HighOrderSort = declaration.Important ? importantHighOrderSort : notImportantHighOrderSort,
+ IdAttributesInSelector = 0,
+ AttributesInSelector = 0,
+ ElementNamesInSelector = 0,
+ SequenceNumber = propertySequence++,
+ };
+ AddPropertyToElement(element, prop);
+ }
+ }
+
+ private static void ApplyCssToElement(
+ CssDocument cssDoc,
+ XElement element,
+ Property.HighOrderPriority notImportantHighOrderSort,
+ Property.HighOrderPriority importantHighOrderSort,
+ ref int propertySequence)
+ {
+ foreach (var ruleSet in cssDoc.RuleSets)
+ {
+ ApplyDeclarationsToElement(ruleSet, element, notImportantHighOrderSort, importantHighOrderSort, ref propertySequence);
+ }
+ }
+
+ private static void ApplyStyleAttributes(XElement xHtml, ref int propertySequence)
+ {
+ foreach (var element in xHtml.DescendantsAndSelf())
+ {
+ XAttribute styleAtt = element.Attribute(XhtmlNoNamespace.style);
+ if (styleAtt != null)
+ {
+ string style = (string)styleAtt;
+ string cssString = element.Name + "{" + style + "}";
+ cssString = cssString.Replace('\"', '\'');
+ CssParser cssParser = new CssParser();
+ CssDocument cssDoc = cssParser.ParseText(cssString);
+ ApplyCssToElement(
+ cssDoc,
+ element,
+ Property.HighOrderPriority.StyleAttributeNormal,
+ Property.HighOrderPriority.StyleAttributeHigh,
+ ref propertySequence);
+ }
+ XAttribute dirAtt = element.Attribute(XhtmlNoNamespace.dir);
+ if (dirAtt != null)
+ {
+ string dir = dirAtt.Value.ToLower();
+ Property prop = new Property()
+ {
+ Name = "direction",
+ Expression = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = dir, Type = CssTermType.String } } },
+ HighOrderSort = Property.HighOrderPriority.HtmlAttribute,
+ IdAttributesInSelector = 0,
+ AttributesInSelector = 0,
+ ElementNamesInSelector = 0,
+ SequenceNumber = propertySequence++,
+ };
+ AddPropertyToElement(element, prop);
+ }
+ }
+ }
+
+ private enum CssDataType
+ {
+ BorderWidth,
+ BorderStyle,
+ Color,
+ ListStyleType,
+ ListStylePosition,
+ ListStyleImage,
+ BackgroundColor,
+ BackgroundImage,
+ BackgroundRepeat,
+ BackgroundAttachment,
+ BackgroundPosition,
+ FontStyle,
+ FontVarient,
+ FontWeight,
+ FontSize,
+ LineHeight,
+ FontFamily,
+ Length,
+ };
+
+ private class ShorthandPropertiesInfo
+ {
+ public string Name;
+ public string Pattern;
+ }
+
+ private static ShorthandPropertiesInfo[] ShorthandProperties = new[]
+ {
+ new ShorthandPropertiesInfo
+ {
+ Name = "margin",
+ Pattern = "margin-{0}",
+ },
+ new ShorthandPropertiesInfo
+ {
+ Name = "padding",
+ Pattern = "padding-{0}",
+ },
+ new ShorthandPropertiesInfo
+ {
+ Name = "border-width",
+ Pattern = "border-{0}-width",
+ },
+ new ShorthandPropertiesInfo
+ {
+ Name = "border-color",
+ Pattern = "border-{0}-color",
+ },
+ new ShorthandPropertiesInfo
+ {
+ Name = "border-style",
+ Pattern = "border-{0}-style",
+ },
+ };
+
+ private static void ExpandShorthandProperties(XElement xHtml, HtmlToWmlConverterSettings settings)
+ {
+ foreach (var element in xHtml.DescendantsAndSelf())
+ {
+ ExpandShorthandPropertiesForElement(element, settings);
+ }
+ }
+
+ private static void ExpandShorthandPropertiesForElement(XElement element, HtmlToWmlConverterSettings settings)
+ {
+ Dictionary<string, Property> propertyList = element.Annotation<Dictionary<string, Property>>();
+ if (propertyList == null)
+ {
+ propertyList = new Dictionary<string, Property>();
+ element.AddAnnotation(propertyList);
+ }
+ foreach (var kvp in propertyList.ToList())
+ {
+ Property p = kvp.Value;
+ if (p.Name == "border")
+ {
+ CssExpression borderColor;
+ CssExpression borderWidth;
+ CssExpression borderStyle;
+ if (p.Expression.Terms.Count == 1 && p.Expression.Terms.First().Value == "inherit")
+ {
+ borderColor = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ borderWidth = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ borderStyle = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ }
+ else
+ {
+ //borderColor = GetComputedPropertyValue(null, element, "color", settings);
+ //borderWidth = new Expression { Terms = new List<Term> { new Term { Value = "medium", Type = TermType.String } } };
+ //borderStyle = new Expression { Terms = new List<Term> { new Term { Value = "none", Type = TermType.String } } };
+ borderColor = null;
+ borderWidth = null;
+ borderStyle = null;
+ foreach (var term in p.Expression.Terms)
+ {
+ CssDataType dataType = GetDatatypeFromBorderTerm(term);
+ switch (dataType)
+ {
+ case CssDataType.Color:
+ borderColor = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BorderWidth:
+ borderWidth = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BorderStyle:
+ borderStyle = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ }
+ }
+ }
+ foreach (var side in new[] { "top", "left", "bottom", "right" })
+ {
+ if (borderWidth != null)
+ {
+ Property bwp = new Property
+ {
+ Name = "border-" + side + "-width",
+ Expression = borderWidth,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bwp);
+ }
+ if (borderStyle != null)
+ {
+ Property bsp = new Property
+ {
+ Name = "border-" + side + "-style",
+ Expression = borderStyle,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bsp);
+ }
+ if (borderColor != null)
+ {
+ Property bc = new Property
+ {
+ Name = "border-" + side + "-color",
+ Expression = borderColor,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bc);
+ }
+ }
+ continue;
+ }
+ if (p.Name == "border-top" || p.Name == "border-right" || p.Name == "border-bottom" || p.Name == "border-left")
+ {
+ CssExpression borderColor;
+ CssExpression borderWidth;
+ CssExpression borderStyle;
+ if (p.Expression.Terms.Count() == 1 && p.Expression.Terms.First().Value == "inherit")
+ {
+ borderColor = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ borderWidth = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ borderStyle = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ }
+ else
+ {
+ //borderColor = GetComputedPropertyValue(null, element, "color", settings);
+ //borderWidth = new Expression { Terms = new List<Term> { new Term { Value = "medium", Type = TermType.String } } };
+ //borderStyle = new Expression { Terms = new List<Term> { new Term { Value = "none", Type = TermType.String } } };
+ borderColor = null;
+ borderWidth = null;
+ borderStyle = null;
+ foreach (var term in p.Expression.Terms)
+ {
+ CssDataType dataType = GetDatatypeFromBorderTerm(term);
+ switch (dataType)
+ {
+ case CssDataType.Color:
+ borderColor = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BorderWidth:
+ borderWidth = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BorderStyle:
+ borderStyle = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ }
+ }
+ }
+ if (borderWidth != null)
+ {
+ Property bwp = new Property
+ {
+ Name = p.Name + "-width",
+ Expression = borderWidth,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bwp);
+ }
+ if (borderStyle != null)
+ {
+ Property bsp = new Property
+ {
+ Name = p.Name + "-style",
+ Expression = borderStyle,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bsp);
+ }
+ if (borderColor != null)
+ {
+ Property bc = new Property
+ {
+ Name = p.Name + "-color",
+ Expression = borderColor,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bc);
+ }
+ continue;
+ }
+
+ if (p.Name == "list-style")
+ {
+ CssExpression listStyleType;
+ CssExpression listStylePosition;
+ CssExpression listStyleImage;
+ if (p.Expression.Terms.Count == 1 && p.Expression.Terms.First().Value == "inherit")
+ {
+ listStyleType = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ listStylePosition = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ listStyleImage = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ }
+ else
+ {
+ listStyleType = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "disc", Type = CssTermType.String } } };
+ listStylePosition = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "outside", Type = CssTermType.String } } };
+ listStyleImage = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = CssTermType.String } } };
+ foreach (var term in p.Expression.Terms)
+ {
+ CssDataType dataType = GetDatatypeFromListStyleTerm(term);
+ switch (dataType)
+ {
+ case CssDataType.ListStyleType:
+ listStyleType = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.ListStylePosition:
+ listStylePosition = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.ListStyleImage:
+ listStyleImage = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ }
+ }
+ }
+ Property lst = new Property
+ {
+ Name = "list-style-type",
+ Expression = listStyleType,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, lst);
+ Property lsp = new Property
+ {
+ Name = "list-style-position",
+ Expression = listStylePosition,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, lsp);
+ Property lsi = new Property
+ {
+ Name = "list-style-image",
+ Expression = listStyleImage,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, lsi);
+ continue;
+ }
+
+ if (p.Name == "background")
+ {
+ CssExpression backgroundColor;
+ CssExpression backgroundImage;
+ CssExpression backgroundRepeat;
+ CssExpression backgroundAttachment;
+ CssExpression backgroundPosition;
+ if (p.Expression.Terms.Count == 1 && p.Expression.Terms.First().Value == "inherit")
+ {
+ backgroundColor = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ backgroundImage = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ backgroundRepeat = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ backgroundAttachment = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ backgroundPosition = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ }
+ else
+ {
+ backgroundColor = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "transparent", Type = CssTermType.String } } };
+ backgroundImage = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "none", Type = CssTermType.String } } };
+ backgroundRepeat = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "repeat", Type = CssTermType.String } } };
+ backgroundAttachment = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "scroll", Type = CssTermType.String } } };
+ backgroundPosition = new CssExpression
+ {
+ Terms = new List<CssTerm> {
+ new CssTerm {
+ Value = "0",
+ Unit = OpenXmlPowerTools.HtmlToWml.CSS.CssUnit.Percent,
+ Type = CssTermType.Number },
+ new CssTerm {
+ Value = "0",
+ Unit = OpenXmlPowerTools.HtmlToWml.CSS.CssUnit.Percent,
+ Type = CssTermType.Number },
+ }
+ };
+ List<CssTerm> backgroundPositionList = new List<CssTerm>();
+ foreach (var term in p.Expression.Terms)
+ {
+ CssDataType dataType = GetDatatypeFromBackgroundTerm(term);
+ switch (dataType)
+ {
+ case CssDataType.BackgroundColor:
+ backgroundColor = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BackgroundImage:
+ backgroundImage = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BackgroundRepeat:
+ backgroundRepeat = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BackgroundAttachment:
+ backgroundAttachment = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.BackgroundPosition:
+ backgroundPositionList.Add(term);
+ break;
+ }
+ }
+ if (backgroundPositionList.Count() == 1)
+ {
+ backgroundPosition = new CssExpression
+ {
+ Terms = new List<CssTerm> {
+ backgroundPositionList.First(),
+ new CssTerm {
+ Value = "center",
+ Type = CssTermType.String
+ },
+ }
+ };
+ }
+ if (backgroundPositionList.Count() == 2)
+ {
+ backgroundPosition = new CssExpression
+ {
+ Terms = backgroundPositionList
+ };
+ }
+ }
+ Property bc = new Property
+ {
+ Name = "background-color",
+ Expression = backgroundColor,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bc);
+ Property bgi = new Property
+ {
+ Name = "background-image",
+ Expression = backgroundImage,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bgi);
+ Property bgr = new Property
+ {
+ Name = "background-repeat",
+ Expression = backgroundRepeat,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bgr);
+ Property bga = new Property
+ {
+ Name = "background-attachment",
+ Expression = backgroundAttachment,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bga);
+ Property bgp = new Property
+ {
+ Name = "background-position",
+ Expression = backgroundPosition,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, bgp);
+ continue;
+ }
+
+ if (p.Name == "font")
+ {
+ CssExpression fontStyle;
+ CssExpression fontVarient;
+ CssExpression fontWeight;
+ CssExpression fontSize;
+ CssExpression lineHeight;
+ CssExpression fontFamily;
+ if (p.Expression.Terms.Count() == 1 && p.Expression.Terms.First().Value == "inherit")
+ {
+ fontStyle = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ fontVarient = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ fontWeight = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ fontSize = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ lineHeight = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ fontFamily = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "inherit", Type = CssTermType.String } } };
+ }
+ else
+ {
+ fontStyle = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = CssTermType.String } } };
+ fontVarient = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = CssTermType.String } } };
+ fontWeight = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = CssTermType.String } } };
+ fontSize = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "medium", Type = CssTermType.String } } };
+ lineHeight = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "normal", Type = CssTermType.String } } };
+ fontFamily = new CssExpression { Terms = new List<CssTerm> { new CssTerm { Value = "serif", Type = CssTermType.String } } };
+ List<CssTerm> fontFamilyList = new List<CssTerm>();
+ foreach (var term in p.Expression.Terms)
+ {
+ CssDataType dataType = GetDatatypeFromFontTerm(term);
+ switch (dataType)
+ {
+ case CssDataType.FontStyle:
+ fontStyle = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.FontVarient:
+ fontVarient = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.FontWeight:
+ fontWeight = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.FontSize:
+ fontSize = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.Length:
+ if (term.SeparatorChar == "/")
+ lineHeight = new CssExpression { Terms = new List<CssTerm> { term } };
+ else
+ fontSize = new CssExpression { Terms = new List<CssTerm> { term } };
+ break;
+ case CssDataType.FontFamily:
+ fontFamilyList.Add(term);
+ break;
+ }
+ }
+ if (fontFamilyList.Count > 0)
+ fontFamily = new CssExpression { Terms = fontFamilyList };
+ }
+ Property fs = new Property
+ {
+ Name = "font-style",
+ Expression = fontStyle,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, fs);
+ Property fv = new Property
+ {
+ Name = "font-varient",
+ Expression = fontVarient,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, fv);
+ Property fw = new Property
+ {
+ Name = "font-weight",
+ Expression = fontWeight,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, fw);
+ Property fsz = new Property
+ {
+ Name = "font-size",
+ Expression = fontSize,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, fsz);
+ Property lh = new Property
+ {
+ Name = "line-height",
+ Expression = lineHeight,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, lh);
+ Property ff = new Property
+ {
+ Name = "font-family",
+ Expression = fontFamily,
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ff);
+ continue;
+ }
+
+ foreach (var shPr in ShorthandProperties)
+ {
+ if (p.Name == shPr.Name)
+ {
+ switch (p.Expression.Terms.Count)
+ {
+ case 1:
+ foreach (var direction in new[] { "top", "right", "bottom", "left" })
+ {
+ Property ep = new Property()
+ {
+ Name = String.Format(shPr.Pattern, direction),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep);
+ }
+ break;
+ case 2:
+ foreach (var direction in new[] { "top", "bottom" })
+ {
+ Property ep = new Property()
+ {
+ Name = String.Format(shPr.Pattern, direction),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep);
+ }
+ foreach (var direction in new[] { "left", "right" })
+ {
+ Property ep = new Property()
+ {
+ Name = String.Format(shPr.Pattern, direction),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.Skip(1).First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep);
+ }
+ break;
+ case 3:
+ Property ep3 = new Property()
+ {
+ Name = String.Format(shPr.Pattern, "top"),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep3);
+ foreach (var direction in new[] { "left", "right" })
+ {
+ Property ep2 = new Property()
+ {
+ Name = String.Format(shPr.Pattern, direction),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.Skip(1).First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep2);
+ }
+ Property ep4 = new Property()
+ {
+ Name = String.Format(shPr.Pattern, "bottom"),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.Skip(2).First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep4);
+ break;
+ case 4:
+ int skip = 0;
+ foreach (var direction in new[] { "top", "right", "bottom", "left" })
+ {
+ Property ep = new Property()
+ {
+ Name = String.Format(shPr.Pattern, direction),
+ Expression = new CssExpression { Terms = new List<CssTerm> { p.Expression.Terms.Skip(skip++).First() } },
+ HighOrderSort = p.HighOrderSort,
+ IdAttributesInSelector = p.IdAttributesInSelector,
+ AttributesInSelector = p.AttributesInSelector,
+ ElementNamesInSelector = p.ElementNamesInSelector,
+ SequenceNumber = p.SequenceNumber,
+ };
+ AddPropertyToDictionary(propertyList, ep);
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private static string[] BackgroundRepeatValues = new[]
+ {
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "no-repeat",
+ };
+
+ private static string[] BackgroundAttachmentValues = new[]
+ {
+ "scroll",
+ "fixed",
+ };
+
+ private static string[] BackgroundPositionValues = new[]
+ {
+ "left",
+ "right",
+ "top",
+ "bottom",
+ "center",
+ };
+
+ private static CssDataType GetDatatypeFromBackgroundTerm(CssTerm term)
+ {
+ if (term.IsColor)
+ return CssDataType.BackgroundColor;
+ if (BackgroundRepeatValues.Contains(term.Value.ToLower()))
+ return CssDataType.BackgroundRepeat;
+ if (BackgroundAttachmentValues.Contains(term.Value.ToLower()))
+ return CssDataType.BackgroundAttachment;
+ if (term.Function != null)
+ return CssDataType.BackgroundImage;
+ if (term.Unit == CssUnit.CM ||
+ term.Unit == CssUnit.EM ||
+ term.Unit == CssUnit.IN ||
+ term.Unit == CssUnit.MM ||
+ term.Unit == CssUnit.PT ||
+ term.Unit == CssUnit.PX ||
+ term.Unit == CssUnit.Percent)
+ return CssDataType.BackgroundPosition;
+ if (BackgroundPositionValues.Contains(term.Value.ToLower()))
+ return CssDataType.BackgroundPosition;
+ return CssDataType.BackgroundPosition;
+ }
+
+ private static string[] ListStylePositionValues = new[]
+ {
+ "inside",
+ "outside",
+ };
+
+ private static string[] BorderStyleValues = new[]
+ {
+ "none",
+ "hidden",
+ "dotted",
+ "dashed",
+ "solid",
+ "double",
+ "groove",
+ "ridge",
+ "inset",
+ "outset",
+ };
+
+ private static CssDataType GetDatatypeFromBorderTerm(CssTerm term)
+ {
+ if (term.IsColor)
+ {
+ return CssDataType.Color;
+ }
+ if (BorderStyleValues.Contains(term.Value.ToLower()))
+ return CssDataType.BorderStyle;
+ return CssDataType.BorderWidth;
+ }
+
+ private static string[] ListStyleTypeValues = new[]
+ {
+ "disc",
+ "circle",
+ "square",
+ "decimal",
+ "decimal-leading-zero",
+ "lower-roman",
+ "upper-roman",
+ "lower-greek",
+ "lower-latin",
+ "upper-latin",
+ "armenian",
+ "georgian",
+ "lower-alpha",
+ "upper-alpha",
+ };
+
+ private static CssDataType GetDatatypeFromListStyleTerm(CssTerm term)
+ {
+ if (ListStyleTypeValues.Contains(term.Value.ToLower()))
+ return CssDataType.ListStyleType;
+ if (ListStylePositionValues.Contains(term.Value.ToLower()))
+ return CssDataType.ListStylePosition;
+ return CssDataType.ListStyleImage;
+ }
+
+ private static string[] FontStyleValues = new[]
+ {
+ "italic",
+ "oblique",
+ };
+
+ private static string[] FontVarientValues = new[]
+ {
+ "small-caps",
+ };
+
+ private static string[] FontWeightValues = new[]
+ {
+ "bold",
+ "bolder",
+ "lighter",
+ "100",
+ "200",
+ "300",
+ "400",
+ "500",
+ "600",
+ "700",
+ "800",
+ "900",
+ };
+
+ private static CssDataType GetDatatypeFromFontTerm(CssTerm term)
+ {
+ if (FontStyleValues.Contains(term.Value.ToLower()))
+ return CssDataType.FontStyle;
+ if (FontVarientValues.Contains(term.Value.ToLower()))
+ return CssDataType.FontVarient;
+ if (FontWeightValues.Contains(term.Value.ToLower()))
+ return CssDataType.FontWeight;
+ if (FontSizeMap.ContainsKey(term.Value.ToLower()))
+ return CssDataType.FontSize;
+ if (term.Unit == CssUnit.CM ||
+ term.Unit == CssUnit.EM ||
+ term.Unit == CssUnit.IN ||
+ term.Unit == CssUnit.MM ||
+ term.Unit == CssUnit.PT ||
+ term.Unit == CssUnit.PX ||
+ term.Unit == CssUnit.Percent)
+ return CssDataType.Length;
+ return CssDataType.FontFamily;
+ }
+
+ public class PropertyInfo
+ {
+ public string[] Names;
+ public bool Inherits;
+ public Func<XElement, HtmlToWmlConverterSettings, bool> Includes;
+ public Func<XElement, HtmlToWmlConverterSettings, CssExpression> InitialValue;
+ public Func<XElement, CssExpression, HtmlToWmlConverterSettings, CssExpression> ComputedValue;
+ }
+
+ private static void WriteXHtmlWithAnnotations(XElement element, StringBuilder sb)
+ {
+ int depth = element.Ancestors().Count() * 2;
+ XElement dummyElement = new XElement(element.Name, element.Attributes());
+ sb.Append(String.Format("{0}{1}", "".PadRight(depth), dummyElement) + Environment.NewLine);
+ Dictionary<string, Property> propList = element.Annotation<Dictionary<string, Property>>();
+ if (propList != null)
+ {
+ sb.Append("".PadRight(depth + 2) + "Properties from Stylesheets" + Environment.NewLine);
+ sb.Append("".PadRight(depth + 2) + "===========================" + Environment.NewLine);
+ foreach (var kvp in propList.OrderBy(p => p.Key).ThenBy(p => p.Value))
+ {
+ Property prop = kvp.Value;
+ string propString = String.Format("{0} High:{1} Id:{2} Att:{3} Ell:{4} Seq:{5}",
+ (prop.Name + ":" + prop.Expression + " ").PadRight(50 - depth + 2, '.'), (int)prop.HighOrderSort, prop.IdAttributesInSelector, prop.AttributesInSelector,
+ prop.ElementNamesInSelector, prop.SequenceNumber);
+ sb.Append(String.Format("{0}{1}", "".PadRight(depth + 2), propString) + Environment.NewLine);
+ }
+ sb.Append(Environment.NewLine);
+ }
+ Dictionary<string, CssExpression> computedProperties = element.Annotation<Dictionary<string, CssExpression>>();
+ if (computedProperties != null)
+ {
+ sb.Append("".PadRight(depth + 2) + "Computed Properties" + Environment.NewLine);
+ sb.Append("".PadRight(depth + 2) + "===================" + Environment.NewLine);
+ foreach (var prop in computedProperties.OrderBy(cp => cp.Key))
+ {
+ string propString = prop.Key + ":" + prop.Value;
+ sb.Append(String.Format("{0}{1}", "".PadRight(depth + 2), propString) + Environment.NewLine);
+ }
+ sb.Append(Environment.NewLine);
+ }
+ foreach (var child in element.Elements())
+ WriteXHtmlWithAnnotations(child, sb);
+ }
+
+ public static string DumpCss(CssDocument css)
+ {
+ StringBuilder sb = new StringBuilder();
+ int indent = 0;
+
+ Pr(sb, indent, "CSS Tree Dump");
+ Pr(sb, indent, "=============");
+
+ Pr(sb, indent, "Directives count: {0}", css.Directives.Count());
+ Pr(sb, indent, "RuleSet count: {0}", css.RuleSets.Count());
+ foreach (var rs in css.RuleSets)
+ DumpRuleSet(sb, indent, rs);
+
+ Pr(sb, indent, "");
+ return sb.ToString();
+ }
+
+ private static void DumpFunction(StringBuilder sb, int indent, CssFunction f)
+ {
+ Pr(sb, indent, "Function: {0}", f);
+ if (f != null)
+ {
+ indent++;
+ Pr(sb, indent, "Name: {0}", f.Name);
+ DumpExpression(sb, indent, f.Expression);
+ indent--;
+ }
+ }
+
+ private static void DumpAttribute(StringBuilder sb, int indent, OpenXmlPowerTools.HtmlToWml.CSS.CssAttribute a)
+ {
+ Pr(sb, indent, "Attribute: {0}", a);
+ if (a != null)
+ {
+ indent++;
+ Pr(sb, indent, "Operand: {0}", a.Operand);
+ Pr(sb, indent, "Operator: {0}", a.Operator);
+ Pr(sb, indent, "OperatorString: {0}", a.CssOperatorString);
+ Pr(sb, indent, "Value: {0}", a.Value);
+ indent--;
+ }
+ }
+
+ private static void DumpSimpleSelector(StringBuilder sb, int indent, CssSimpleSelector s)
+ {
+ indent++;
+ Pr(sb, indent, "SimpleSelector: {0}", s);
+ if (s != null)
+ {
+ indent++;
+ DumpAttribute(sb, indent, s.Attribute);
+ Pr(sb, indent, "Child: {0}", s.Child);
+ DumpSimpleSelector(sb, indent, s.Child);
+ Pr(sb, indent, "Class: {0}", s.Class);
+ Pr(sb, indent, "Combinator: {0}", s.Combinator);
+ Pr(sb, indent, "CombinatorString: {0}", s.CombinatorString);
+ Pr(sb, indent, "ElementName: >{0}<", s.ElementName);
+ DumpFunction(sb, indent, s.Function);
+ Pr(sb, indent, "ID: {0}", s.ID);
+ Pr(sb, indent, "Pseudo: {0}", s.Pseudo);
+ indent--;
+ }
+ indent--;
+ }
+
+ private static void DumpSelectors(StringBuilder sb, int indent, CssSelector s)
+ {
+ indent++;
+ Pr(sb, indent, "SimpleSelectors count: {0}", s.SimpleSelectors.Count());
+ foreach (var ss in s.SimpleSelectors)
+ DumpSimpleSelector(sb, indent, ss);
+ indent--;
+ }
+
+ private static void DumpTerm(StringBuilder sb, int indent, CssTerm t)
+ {
+ Pr(sb, indent, "Term >{0}<", t.ToString());
+ indent++;
+ DumpFunction(sb, indent, t.Function);
+ Pr(sb, indent, "IsColor: {0}", t.IsColor);
+ Pr(sb, indent, "Separator: {0}", t.Separator);
+ Pr(sb, indent, "SeparatorChar: {0}", t.SeparatorChar);
+ Pr(sb, indent, "Sign: {0}", t.Sign);
+ Pr(sb, indent, "SignChar: {0}", t.SignChar);
+ Pr(sb, indent, "Type: {0}", t.Type);
+ Pr(sb, indent, "Unit: {0}", t.Unit);
+ Pr(sb, indent, "UnitString: {0}", t.UnitString);
+ Pr(sb, indent, "Value: {0}", t.Value);
+ indent--;
+ }
+
+ private static void DumpExpression(StringBuilder sb, int indent, CssExpression e)
+ {
+ Pr(sb, indent, "Expression >{0}<", e.ToString());
+ indent++;
+ Pr(sb, indent, "Terms count: {0}", e.Terms.Count());
+ foreach (var t in e.Terms)
+ DumpTerm(sb, indent, t);
+ indent--;
+ }
+
+ private static void DumpDeclarations(StringBuilder sb, int indent, CssDeclaration d)
+ {
+ indent++;
+ Pr(sb, indent, "Declaration >{0}<", d.ToString());
+ indent++;
+ Pr(sb, indent, "Name: {0}", d.Name);
+ DumpExpression(sb, indent, d.Expression);
+ Pr(sb, indent, "Important: {0}", d.Important);
+ indent--;
+ indent--;
+ }
+
+ private static void DumpRuleSet(StringBuilder sb, int indent, CssRuleSet rs)
+ {
+ indent++;
+ Pr(sb, indent, "RuleSet");
+ indent++;
+ Pr(sb, indent, "Selectors count: {0}", rs.Selectors.Count());
+ foreach (var s in rs.Selectors)
+ DumpSelectors(sb, indent, s);
+ Pr(sb, indent, "Declarations count: {0}", rs.Declarations.Count());
+ foreach (var d in rs.Declarations)
+ DumpDeclarations(sb, indent, d);
+ indent--;
+ indent--;
+ }
+
+ private static void Pr(StringBuilder sb, int indent, string format, object o)
+ {
+ if (o == null)
+ return;
+ string text = String.Format(format, o);
+ StringBuilder sb2 = new StringBuilder("".PadRight(indent * 2) + text);
+ //sb2.Replace("&", "&");
+ //sb2.Replace("<", "<");
+ //sb2.Replace(">", ">");
+ sb.Append(sb2);
+ sb.Append(Environment.NewLine);
+ //Console.WriteLine(sb2);
+ }
+
+ private static void Pr(StringBuilder sb, int indent, string text)
+ {
+ StringBuilder sb2 = new StringBuilder("".PadRight(indent * 2) + text);
+ //sb2.Replace("&", "&");
+ //sb2.Replace("<", "<");
+ //sb2.Replace(">", ">");
+ sb.Append(sb2);
+ sb.Append(Environment.NewLine);
+ //Console.WriteLine(sb2);
+ }
+
+ public class Property : IComparable<Property>
+ {
+ public string Name { get; set; }
+ public CssExpression Expression { get; set; }
+ public HighOrderPriority HighOrderSort { get; set; }
+ public int IdAttributesInSelector { get; set; }
+ public int AttributesInSelector { get; set; }
+ public int ElementNamesInSelector { get; set; }
+ public int SequenceNumber { get; set; }
+ public enum HighOrderPriority
+ {
+ InitialValue = 0,
+ Inherited = 1,
+ UserAgentNormal = 2,
+ UserAgentHigh = 3,
+ UserNormal = 4,
+ AuthorNormal = 5,
+ HtmlAttribute = 6,
+ StyleAttributeNormal = 7,
+ StyleAttributeHigh = 8,
+ AuthorHigh = 9,
+ UserHigh = 10,
+ };
+
+ int System.IComparable<Property>.CompareTo(Property other)
+ {
+ // if this is less than other, return -1
+ // if this is greater than other, return 1
+
+ int gt = 1;
+ int lt = -1;
+ if (this.HighOrderSort < other.HighOrderSort)
+ return lt;
+ if (this.HighOrderSort > other.HighOrderSort)
+ return gt;
+ if (this.IdAttributesInSelector < other.IdAttributesInSelector)
+ return lt;
+ if (this.IdAttributesInSelector > other.IdAttributesInSelector)
+ return gt;
+ if (this.AttributesInSelector < other.AttributesInSelector)
+ return lt;
+ if (this.AttributesInSelector > other.AttributesInSelector)
+ return gt;
+ if (this.ElementNamesInSelector < other.ElementNamesInSelector)
+ return lt;
+ if (this.ElementNamesInSelector > other.ElementNamesInSelector)
+ return gt;
+ return this.SequenceNumber.CompareTo(other.SequenceNumber);
+ }
+ }
+
+ private static Dictionary<string, string> ColorMap = new Dictionary<string, string>()
+ {
+ { "maroon", "800000" },
+ { "red", "FF0000" },
+ { "orange", "FFA500" },
+ { "yellow", "FFFF00" },
+ { "olive", "808000" },
+ { "purple", "800080" },
+ { "fuchsia", "FF00FF" },
+ { "white", "FFFFFF" },
+ { "lime", "00FF00" },
+ { "green", "008000" },
+ { "navy", "000080" },
+ { "blue", "0000FF" },
+ { "mediumblue", "0000CD" },
+ { "aqua", "00FFFF" },
+ { "teal", "008080" },
+ { "black", "000000" },
+ { "silver", "C0C0C0" },
+ { "gray", "808080" },
+ { "darkgray", "A9A9A9" },
+ { "beige", "F5F5DC" },
+ { "windowtext", "000000" },
+ };
+
+ public static string GetWmlColorFromExpression(CssExpression color)
+ {
+ // todo have to handle all forms of colors here
+ if (color.Terms.Count() == 1)
+ {
+ CssTerm term = color.Terms.First();
+ if (term.Type == CssTermType.Function && term.Function.Name.ToUpper() == "RGB" && term.Function.Expression.Terms.Count == 3)
+ {
+ List<CssTerm> lt = term.Function.Expression.Terms;
+ if (lt.First().Unit == CssUnit.Percent)
+ {
+ string v1 = lt.First().Value;
+ string v2 = lt.ElementAt(1).Value;
+ string v3 = lt.ElementAt(2).Value;
+ string colorInHex = String.Format("{0:x2}{1:x2}{2:x2}", (int)((float.Parse(v1) / 100.0) * 255),
+ (int)((float.Parse(v2) / 100.0) * 255), (int)((float.Parse(v3) / 100.0) * 255));
+ return colorInHex;
+ }
+ else
+ {
+ string v1 = lt.First().Value;
+ string v2 = lt.ElementAt(1).Value;
+ string v3 = lt.ElementAt(2).Value;
+ string colorInHex = String.Format("{0:x2}{1:x2}{2:x2}", int.Parse(v1), int.Parse(v2), int.Parse(v3));
+ return colorInHex;
+ }
+ }
+ string value = term.Value;
+ if (value.Substring(0, 1) == "#" && value.Length == 4)
+ {
+ string e = ConvertSingleDigit(value.Substring(1, 1)) +
+ ConvertSingleDigit(value.Substring(2, 1)) +
+ ConvertSingleDigit(value.Substring(3, 1));
+ return e;
+ }
+ if (value.Substring(0, 1) == "#")
+ return value.Substring(1);
+ if (ColorMap.ContainsKey(value))
+ return ColorMap[value];
+ return value;
+ }
+ return "000000";
+ }
+
+ private static string ConvertSingleDigit(string singleDigit)
+ {
+ return singleDigit + singleDigit;
+ }
+ }
+}
+
+#if false
+color
+Value: <color> | inherit
+Initial: depends on UA
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+margin-top, margin-bottom
+Value: <margin-width> | inherit
+Initial: 0
+Applies to: all elements except elements with table display types other than table-caption, table, and inline-table
+ all elements except th, td, tr
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: the percentage as specified or the absolute length
+
+margin-right, margin-left
+Value: <margin-width> | inherit
+Initial: 0
+Applies to: all elements except elements with table display types other than table-caption, table, and inline-table
+ all elements except th, td, tr
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: the percentage as specified or the absolute length
+
+padding-top, padding-right, padding-bottom, padding-left
+Value: <padding-width> | inherit
+Initial: 0
+Applies to: all elements except table-row-group, table-header-group,
+ table-footer-group, table-row, table-column-group and table-column
+ all elements except tr
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: the percentage as specified or the absolute length
+
+border-top-width, border-right-width, border-bottom-width, border-left-width
+Value: <border-width> | inherit
+Initial: medium
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: absolute length; '0' if the border style is 'none' or 'hidden'
+
+border-top-color, border-right-color, border-bottom-color, border-left-color
+Value: <color> | transparent | inherit
+Initial: the value of the color property
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: when taken from the ’color’ property, the computed value of
+ ’color’; otherwise, as specified
+
+border-top-style, border-right-style, border-bottom-style, border-left-style
+Value: <border-style> | inherit
+Initial: none
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as specified
+
+display
+Value: inline | block | list-item | inline-block | table | inline-table |
+ table-row-group | table-header-group | table-footer-group |
+ table-row | table-column-group | table-column | table-cell |
+ table-caption | none | inherit
+Initial: inline
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: see text
+
+position
+Value: static | relative | absolute | fixed | inherit
+Initial: static
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as specified
+
+top
+Value: <length> | <percentage> | auto | inherit
+Initial: auto
+Applies to: positioned elements
+Inherited: no
+Percentages: refer to height of containing block
+Computed value: if specified as a length, the corresponding absolute length; if
+ specified as a percentage, the specified value; otherwise, ’auto’.
+
+right
+Value: <length> | <percentage> | auto | inherit
+Initial: auto
+Applies to: positioned elements
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: if specified as a length, the corresponding absolute length; if
+ specified as a percentage, the specified value; otherwise, ’auto’.
+
+bottom
+Value: <length> | <percentage> | auto | inherit
+Initial: auto
+Applies to: positioned elements
+Inherited: no
+Percentages: refer to height of containing block
+Computed value: if specified as a length, the corresponding absolute length; if
+ specified as a percentage, the specified value; otherwise, ’auto’.
+
+left
+Value: <length> | <percentage> | auto | inherit
+Initial: auto
+Applies to: positioned elements
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: if specified as a length, the corresponding absolute length; if
+ specified as a percentage, the specified value; otherwise, ’auto’.
+
+float
+Value: left | right | none | inherit
+Initial: none
+Applies to: all, but see 9.7 p. 153
+Inherited: no
+Percentages: N/A
+Computed value: as specified
+
+clear
+Value: none | left | right | both | inherit
+Initial: none
+Applies to: block-level elements
+Inherited: no
+Percentages: N/A
+Computed value: as specified
+
+z-index
+Value: auto | integer | inherit
+Initial: auto
+Applies to: positioned elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+direction
+Value: ltr | rtl | inherit
+Initial: ltr
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+unicode-bidi
+Value: normal | embed | bidi-override | inherit
+Initial: normal
+Applies to: all elements, but see prose
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+width
+Value: <length> | <percentage> | auto | inherit
+Initial: auto
+Applies to: all elements but non-replaced in-line elements, table rows, and row groups
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: the percentage or 'auto' as specified or the absolute length
+
+min-width
+Value: <length> | <percentage> | inherit
+Initial: 0
+Applies to: all elements but non-replaced in-line elements, table rows, and row groups
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: the percentage as spec or the absolute length
+
+max-width
+Value: <length> | <percentage> | none | inherit
+Initial: none
+Applies to: all elements but non-replaced in-line elements, table rows, and row groups
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: the percentage as spec or the absolute length
+
+height
+Value: <length> | <percentage> | auto | inherit
+Initial: auto
+Applies to: all elements but non-replaced in-line elements, table columns, and column groups
+Inherited: no
+Percentages: see prose
+Computed value: the percentage as spec or the absolute length
+
+min-height
+Value: <length> | <percentage> | inherit
+Initial: 0
+Applies to: all elements but non-replaced in-line elements, table columns, and column groups
+Inherited: no
+Percentages: see prose
+Computed value: the percentage as spec or the absolute length
+
+max-height
+Value: <length> | <percentage> | none | inherit
+Initial: none
+Applies to: all elements but non-replaced in-line elements, table columns, and column groups
+Inherited: no
+Percentages: refer to height of containing block
+Computed value: the percentage as spec or the absolute length
+
+line-height
+Value: normal | <number> | <length> | <percentage> | <inherit>
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: refer to the font size of the element itself
+Computed value: for <length> and <percentage> the absolute value, otherwise as specified.
+
+vertical-align
+Value: baseline | sub | super | top | text-top | middle | bottom | text-bottom |
+ <percentage> | <length> | inherit
+Initial: baseline
+Applies to: inline-level and 'table-cell' elements
+Inherited: no
+Percentages: refer to the line height of the element itself
+Computed value: for <length> and <percentage> the absolute length, otherwise as specified.
+
+visibility
+Value: visible | hidden | collapse | inherit
+Initial: visible
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+list-style-type
+Value: disc | circle | square | decimal | decimal-leading-zero |
+ lower-roman | upper-roman | lower-greek | lower-latin |
+ upper-latin | armenian | georgian | lower-alpha | upper-alpha |
+ none | inherit
+Initial: disc
+Applies to: elements with display: list-item
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+list-style-image
+Value: <uri> | none | inherit
+Initial: none
+Applies to: elements with ’display: list-item’
+Inherited: yes
+Percentages: N/A
+Computed value: absolute URI or ’none’
+
+list-style-position
+Value: inside | outside | inherit
+Initial: outside
+Applies to: elements with ’display: list-item’
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+background-color
+Value: <color> | transparent | inherit
+Initial: transparent
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+font-family
+Value: [[ <family-name> | <generic-family> ] [, <family-name>|
+ <generic-family>]* ] | inherit
+Initial: depends on user agent
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+font-style
+Value: normal | italic | oblique | inherit
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+font-variant
+Value: normal | small-caps | inherit
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+font-weight
+Value: normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 |
+ 600 | 700 | 800 | 900 | inherit
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: see text
+
+font-size
+Value: <absolute-size> | <relative-size> | <length> | <percentage> |
+ inherit
+Initial: medium
+Applies to: all elements
+Inherited: yes
+Percentages: refer to inherited font size
+Computed value: absolute length
+
+text-indent
+Value: <length> | <percentage> | inherit
+Initial: 0
+Applies to: block containers
+Inherited: yes
+Percentages: refer to width of containing block
+Computed value: the percentage as specified or the absolute length
+
+text-align
+Value: left | right | center | justify | inherit
+Initial: a nameless value that acts as ’left’ if ’direction’ is ’ltr’, ’right’ if
+ ’direction’ is ’rtl’
+Applies to: block containers
+Inherited: yes
+Percentages: N/A
+Computed value: the initial value or as spec
+
+text-decoration
+Value: none | [ underline || overline || line-through || blink ] | inherit
+Initial: none
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+letter-spacing
+Value: normal | <length> | inherit
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: ’normal’ or absolute length
+
+word-spacing
+Value: normal | <length> | inherit
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: for ’normal’ the value 0; otherwise the absolute length
+
+text-transform
+Value: capitalize | uppercase | lowercase | none | inherit
+Initial: none
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+white-space
+Value: normal | pre | nowrap | pre-wrap | pre-line | inherit
+Initial: normal
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+caption-side
+Value: top | bottom | inherit
+Initial: top
+Applies to: 'table-caption' elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+table-layout
+Value: auto | fixed | inherit
+Initial: auto
+Applies to: ’table’ and ’inline-table’ elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+border-collapse
+Value: collapse | separate | inherit
+Initial: separate
+Applies to: ’table’ and ’inline-table’ elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+border-spacing
+Value: <length> <length>? | inherit
+Initial: 0
+Applies to: ’table’ and ’inline-table’ elements
+Inherited: yes
+Percentages: N/A
+Computed value: two absolute lengths
+
+empty-cells
+Value: show | hide | inherit
+Initial: show
+Applies to: 'table-cell' elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+Shorthand Properties
+
+margin
+Value: <margin-width>{1,4} | inherit
+Initial: see individual props
+Applies to: all elements except elements with table display types other than table-caption, table, and inline-table
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: see individual props
+ set one - sets all four values
+ set two - first is top/bottom, second is right/left
+ set three - first is top, second is right/left, third is bottom
+ set four - top right bottom left
+
+padding
+Value: <padding-width>{1-4} | inherit
+Initial: see individual props
+Applies to: all elements except table-row-group, table-header-group,
+ table-footer-group, table-row, table-column-group and table-column
+Inherited: no
+Percentages: refer to width of containing block
+Computed value: see individual props
+ set one - sets all four values
+ set two - first is top/bottom, second is right/left
+ set four - top right bottom left
+
+border-width
+Value: <border-width>{1-4} | inherit
+Initial: see individual props
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: see individual props
+ set one - sets all four values
+ set two - first is top/bottom, second is right/left
+ set four - top right bottom left
+
+border-color
+Value: [<color> | transparent]{1-4} | inherit
+Initial: see individual props
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: see individual props
+ set one - sets all four values
+ set two - first is top/bottom, second is right/left
+ set four - top right bottom left
+
+border-style
+Value: <border-style>{1-4} | inherit
+Initial: see individual props
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: see individual props
+ set one - sets all four values
+ set two - first is top/bottom, second is right/left
+ set four - top right bottom left
+
+border-top, border-right, border-bottom, border-left, border
+Value: [<border-width> || <border-style> || <border-top-color>] | inherit
+Initial: see individ
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: see individ
+
+list-style
+Value: [ <’list-style-type’> || <’list-style-position’> || <’list-style-image’>
+ ] | inherit
+Initial: see individual props
+Applies to: elements with ’display: list-item’
+Inherited: yes
+Percentages: N/A
+Computed value: see individual props
+
+background
+Value: [<’background-color’> || <’background-image’> || <’background-
+ repeat’> || <’background-attachment’> || <’background-
+ position’>] | inherit
+Initial: see individual props
+Applies to: all elements
+Inherited: no
+Percentages: allowed on background-position
+Computed value: see individual props
+
+font
+Value: [ [ <’font-style’> || <’font-variant’> || <’font-weight’> ]?
+ <’font-size’> [ / <’line-height’> ]? <’font-family’> ] | caption |
+ icon | menu | message-box | small-caption | status-bar |
+ inherit
+Initial: see individual props
+Applies to: all elements
+Inherited: yes
+Percentages: see individual props
+Computed value: see individual props
+
+probably not support
+
+overflow
+Value: visible | hidden | scroll | auto | inherit
+Initial: visible
+Applies to: block containers
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+clip
+Value: <shape> | auto | inherit
+Initial: auto
+Applies to: absolutely positioned elements
+Inherited: no
+Percentages: N/A
+Computed value: auto if spec as 'auto', otherwise a rectangle with four values, each of which is
+ 'auto' if spec as 'auto' and the computed length otherwise
+
+content
+Value: normal | none | [ <string> | <uri> | <counter> | attr(<identifier>)
+ | open-quote | close-quote | no-open-quote | no-close-quote
+ ]+ | inherit
+Initial: normal
+Applies to: :before and :after pseudo elements
+Inherited: no
+Percentages: N/A
+Computed value: On elements, always computes to ’normal’. On :before and
+ :after, if ’normal’ is specified, computes to ’none’. Otherwise,
+ for URI values, the absolute URI; for attr() values, the resulting
+ string; for other keywords, as specified.
+
+quotes
+Value: [<string> <string>]+ | none | inherit
+Initial: depends on user agent
+Applies to: all elements
+Inherited: yes
+Percentages: N/A
+Computed value: as spec
+
+counter-reset
+Value: [ <identifier> <integer>? ]+ | none | inherit
+Initial: none
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+counter-increment
+Value: [ <identifier> <integer>? ]+ | none | inherit
+Initial: none
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+background-image
+Value: <uri> | none | inherit
+Initial: none
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: absolute URI or none
+
+background-repeat
+Value: repeat | repeat-x | repeat-y | no-repeat | inherit
+Initial: repeat
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+background-attachment
+Value: scroll | fixed | inherit
+Initial: scroll
+Applies to: all elements
+Inherited: no
+Percentages: N/A
+Computed value: as spec
+
+background-position
+Value: [ [ <percentage> | <length> | left | center | right ] [ <percentage>
+ | <length> | top | center | bottom ]? ] | [ [ left | center |
+ right ] || [ top | center | bottom ] ] | inherit
+Initial: 0% 0%
+Applies to: all elements
+Inherited: no
+Percentages: refer to the size of the box itself
+Computed value: for <length> the absolute value, otherwise a percentage
+#endif
+
+#if false
+
+background-color
+border-bottom-color
+border-bottom-style
+border-bottom-width
+border-collapse
+border-left-color
+border-left-style
+border-left-width
+border-right-color
+border-right-style
+border-right-width
+border-spacing
+border-top-color
+border-top-style
+border-top-width
+bottom
+caption-side
+clear
+color
+direction
+display
+empty-cells
+float
+font-family
+font-size
+font-style
+font-variant
+font-weight
+height
+left
+letter-spacing
+line-height
+list-style-image
+list-style-position
+list-style-type
+margin-bottom
+margin-left
+margin-right
+margin-top
+max-height
+max-width
+min-height
+min-width
+padding-bottom
+padding-left
+padding-right
+padding-top
+position
+right
+table-layout
+text-align
+text-decoration
+text-indent
+text-transform
+top
+unicode-bidi
+vertical-align
+visibility
+white-space
+width
+word-spacing
+z-index
+
+attributes
+==========
+meta
+style
+_class
+href
+
+don't know
+==========
+colspan
+caption
+title
+hr
+border
+http_equiv
+content
+name
+width
+height
+src
+alt
+id
+descr
+type
+
+elements
+========
+html
+head
+body
+div
+p
+h1
+h2
+h3
+h4
+h5
+h6
+h7
+h8
+h9
+a
+b
+table
+tr
+td
+br
+img
+span
+blockquote
+sub
+sup
+ol
+ul
+li
+strong
+em
+tbody
+
+#endif
+
diff --git a/OpenXmlPowerTools/HtmlToWmlCssParser.cs b/OpenXmlPowerTools/HtmlToWmlCssParser.cs
new file mode 100644
index 0000000..da489db
--- /dev/null
+++ b/OpenXmlPowerTools/HtmlToWmlCssParser.cs
@@ -0,0 +1,4394 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+***************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace OpenXmlPowerTools.HtmlToWml.CSS
+{
+ public class CssAttribute
+ {
+ private string m_operand;
+ private CssAttributeOperator? m_op = null;
+ private string m_val;
+
+ public string Operand
+ {
+ get {
+ return m_operand;
+ }
+ set {
+ m_operand = value;
+ }
+ }
+
+ public CssAttributeOperator? Operator
+ {
+ get {
+ return m_op;
+ }
+ set {
+ m_op = value;
+ }
+ }
+
+ public string CssOperatorString
+ {
+ get {
+ if (this.m_op.HasValue)
+ {
+ return this.m_op.Value.ToString();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ set {
+ this.m_op = (CssAttributeOperator)Enum.Parse(typeof(CssAttributeOperator), value);
+ }
+ }
+
+ public string Value
+ {
+ get {
+ return m_val;
+ }
+ set {
+ m_val = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("[{0}", m_operand);
+ if (m_op.HasValue)
+ {
+ switch (m_op.Value)
+ {
+ case CssAttributeOperator.Equals:
+ sb.Append("=");
+ break;
+ case CssAttributeOperator.InList:
+ sb.Append("~=");
+ break;
+ case CssAttributeOperator.Hyphenated:
+ sb.Append("|=");
+ break;
+ case CssAttributeOperator.BeginsWith:
+ sb.Append("$=");
+ break;
+ case CssAttributeOperator.EndsWith:
+ sb.Append("^=");
+ break;
+ case CssAttributeOperator.Contains:
+ sb.Append("*=");
+ break;
+ }
+ sb.Append(m_val);
+ }
+ sb.Append("]");
+ return sb.ToString();
+ }
+ }
+
+ public enum CssAttributeOperator
+ {
+ Equals,
+ InList,
+ Hyphenated,
+ EndsWith,
+ BeginsWith,
+ Contains,
+ }
+
+ public enum CssCombinator
+ {
+ ChildOf,
+ PrecededImmediatelyBy,
+ PrecededBy,
+ }
+
+ public class CssDocument : ItfRuleSetContainer
+ {
+ private List<CssDirective> m_dirs = new List<CssDirective>();
+ private List<CssRuleSet> m_rulesets = new List<CssRuleSet>();
+
+ public List<CssDirective> Directives
+ {
+ get {
+ return m_dirs;
+ }
+ set {
+ m_dirs = value;
+ }
+ }
+
+ public List<CssRuleSet> RuleSets
+ {
+ get {
+ return m_rulesets;
+ }
+ set {
+ m_rulesets = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (CssDirective cssDir in m_dirs)
+ {
+ sb.AppendFormat("{0}" + Environment.NewLine, cssDir.ToString());
+ }
+ if (sb.Length > 0)
+ {
+ sb.Append(Environment.NewLine);
+ }
+ foreach (CssRuleSet rules in m_rulesets)
+ {
+ sb.AppendFormat("{0}" + Environment.NewLine,
+ rules.ToString());
+ }
+ return sb.ToString();
+ }
+ }
+
+ public class CssDeclaration
+ {
+ private string m_name;
+ private CssExpression m_expression;
+ private bool m_important;
+
+ public string Name
+ {
+ get {
+ return m_name;
+ }
+ set {
+ m_name = value;
+ }
+ }
+
+ public bool Important
+ {
+ get {
+ return m_important;
+ }
+ set {
+ m_important = value;
+ }
+ }
+
+ public CssExpression Expression
+ {
+ get {
+ return m_expression;
+ }
+ set {
+ m_expression = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("{0}: {1}{2}",
+ m_name,
+ m_expression.ToString(),
+ m_important ? " !important" : "");
+ return sb.ToString();
+ }
+ }
+
+ public class CssDirective : ItfDeclarationContainer, ItfRuleSetContainer
+ {
+ private CssDirectiveType m_type;
+ private string m_name;
+ private CssExpression m_expression;
+ private List<CssMedium> m_mediums = new List<CssMedium>();
+ private List<CssDirective> m_directives = new List<CssDirective>();
+ private List<CssRuleSet> m_rulesets = new List<CssRuleSet>();
+ private List<CssDeclaration> m_declarations = new List<CssDeclaration>();
+
+ public CssDirectiveType Type
+ {
+ get {
+ return this.m_type;
+ }
+ set {
+ this.m_type = value;
+ }
+ }
+
+ public string Name
+ {
+ get {
+ return this.m_name;
+ }
+ set {
+ this.m_name = value;
+ }
+ }
+
+ public CssExpression Expression
+ {
+ get {
+ return this.m_expression;
+ }
+ set {
+ this.m_expression = value;
+ }
+ }
+
+ public List<CssMedium> Mediums
+ {
+ get {
+ return this.m_mediums;
+ }
+ set {
+ this.m_mediums = value;
+ }
+ }
+
+ public List<CssDirective> Directives
+ {
+ get {
+ return this.m_directives;
+ }
+ set {
+ this.m_directives = value;
+ }
+ }
+
+ public List<CssRuleSet> RuleSets
+ {
+ get {
+ return this.m_rulesets;
+ }
+ set {
+ this.m_rulesets = value;
+ }
+ }
+
+ public List<CssDeclaration> Declarations
+ {
+ get {
+ return this.m_declarations;
+ }
+ set {
+ this.m_declarations = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ return ToString(0);
+ }
+
+ public string ToString(int indentLevel)
+ {
+ string start = "".PadRight(indentLevel, '\t');
+
+ switch (m_type)
+ {
+ case CssDirectiveType.Charset:
+ return ToCharSetString(start);
+ case CssDirectiveType.Page:
+ return ToPageString(start);
+ case CssDirectiveType.Media:
+ return ToMediaString(indentLevel);
+ case CssDirectiveType.Import:
+ return ToImportString();
+ case CssDirectiveType.FontFace:
+ return ToFontFaceString(start);
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.AppendFormat("{0} ", m_name);
+
+ if (m_expression != null)
+ {
+ sb.AppendFormat("{0} ", m_expression);
+ }
+
+ bool first = true;
+ foreach (CssMedium med in m_mediums)
+ {
+ if (first)
+ {
+ first = false;
+ sb.Append(" ");
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.Append(med.ToString());
+ }
+
+ bool HasBlock = (this.m_declarations.Count > 0 || this.m_directives.Count > 0 || this.m_rulesets.Count > 0);
+
+ if (!HasBlock)
+ {
+ sb.Append(";");
+ return sb.ToString();
+ }
+
+ sb.Append(" {" + Environment.NewLine + start);
+
+ foreach (CssDirective dir in m_directives)
+ {
+ sb.AppendFormat("{0}" + Environment.NewLine, dir.ToCharSetString(start + "\t"));
+ }
+
+ foreach (CssRuleSet rules in m_rulesets)
+ {
+ sb.AppendFormat("{0}" + Environment.NewLine, rules.ToString(indentLevel + 1));
+ }
+
+ first = true;
+ foreach (CssDeclaration decl in m_declarations)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(";");
+ }
+ sb.Append(Environment.NewLine + "\t" + start);
+ sb.Append(decl.ToString());
+ }
+
+ sb.Append(Environment.NewLine + "}");
+ return sb.ToString();
+ }
+
+ private string ToFontFaceString(string start)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("@font-face {");
+
+ bool first = true;
+ foreach (CssDeclaration decl in m_declarations)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(";");
+ }
+ sb.Append(Environment.NewLine + "\t" + start);
+ sb.Append(decl.ToString());
+ }
+
+ sb.Append(Environment.NewLine + "}");
+ return sb.ToString();
+ }
+
+ private string ToImportString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("@import ");
+ if (m_expression != null)
+ {
+ sb.AppendFormat("{0} ", m_expression);
+ }
+ bool first = true;
+ foreach (CssMedium med in m_mediums)
+ {
+ if (first)
+ {
+ first = false;
+ sb.Append(" ");
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.Append(med.ToString());
+ }
+ sb.Append(";");
+ return sb.ToString();
+ }
+
+ private string ToMediaString(int indentLevel)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("@media");
+
+ bool first = true;
+ foreach (CssMedium medium in m_mediums)
+ {
+ if (first)
+ {
+ first = false;
+ sb.Append(" ");
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.Append(medium.ToString());
+ }
+ sb.Append(" {" + Environment.NewLine);
+
+ foreach (CssRuleSet ruleset in m_rulesets)
+ {
+ sb.AppendFormat("{0}" + Environment.NewLine, ruleset.ToString(indentLevel + 1));
+ }
+
+ sb.Append("}");
+ return sb.ToString();
+ }
+
+ private string ToPageString(string start)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("@page ");
+ if (m_expression != null)
+ {
+ sb.AppendFormat("{0} ", m_expression);
+ }
+ sb.Append("{" + Environment.NewLine);
+
+ bool first = true;
+ foreach (CssDeclaration decl in m_declarations)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(";");
+ }
+ sb.Append(Environment.NewLine + "\t" + start);
+ sb.Append(decl.ToString());
+ }
+
+ sb.Append("}");
+ return sb.ToString();
+ }
+
+ private string ToCharSetString(string start)
+ {
+ return string.Format("{2}{0} {1}",
+ m_name,
+ m_expression.ToString(),
+ start);
+ }
+ }
+
+ public enum CssDirectiveType
+ {
+ Media,
+ Import,
+ Charset,
+ Page,
+ FontFace,
+ Namespace,
+ Other,
+ }
+
+ public class CssExpression
+ {
+ private List<CssTerm> m_terms = new List<CssTerm>();
+
+ public List<CssTerm> Terms
+ {
+ get {
+ return m_terms;
+ }
+ set {
+ m_terms = value;
+ }
+ }
+
+ public bool IsNotAuto
+ {
+ get
+ {
+ return (this != null && this.ToString() != "auto");
+ }
+ }
+
+ public bool IsAuto
+ {
+ get
+ {
+ return (this != null && this.ToString() == "auto");
+ }
+ }
+
+ public bool IsNotNormal
+ {
+ get
+ {
+ return (this != null && this.ToString() != "normal");
+ }
+ }
+
+ public bool IsNormal
+ {
+ get
+ {
+ return (this != null && this.ToString() == "normal");
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ bool first = true;
+ foreach (CssTerm term in m_terms)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.AppendFormat("{0} ",
+ term.Separator.HasValue ? term.Separator.Value.ToString() : "");
+ }
+ sb.Append(term.ToString());
+ }
+ return sb.ToString();
+ }
+
+ public static implicit operator string(CssExpression e)
+ {
+ return e.ToString();
+ }
+
+ public static explicit operator double(CssExpression e)
+ {
+ return double.Parse(e.Terms.First().Value, CultureInfo.InvariantCulture);
+ }
+
+ public static explicit operator Emu(CssExpression e)
+ {
+ return Emu.PointsToEmus(double.Parse(e.Terms.First().Value, CultureInfo.InvariantCulture));
+ }
+
+ // will only be called on expression that is in terms of points
+ public static explicit operator TPoint(CssExpression e)
+ {
+ return new TPoint(double.Parse(e.Terms.First().Value, CultureInfo.InvariantCulture));
+ }
+
+ // will only be called on expression that is in terms of points
+ public static explicit operator Twip(CssExpression length)
+ {
+ if (length.Terms.Count() == 1)
+ {
+ CssTerm term = length.Terms.First();
+ if (term.Unit == CssUnit.PT)
+ {
+ double ptValue;
+ if (double.TryParse(term.Value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out ptValue))
+ {
+ if (term.Sign == '-')
+ ptValue = -ptValue;
+ return new Twip((long)(ptValue * 20));
+ }
+ }
+ }
+ return 0;
+ }
+ }
+
+ public class CssFunction
+ {
+ private string m_name;
+ private CssExpression m_expression;
+
+ public string Name
+ {
+ get {
+ return m_name;
+ }
+ set {
+ m_name = value;
+ }
+ }
+
+ public CssExpression Expression
+ {
+ get {
+ return m_expression;
+ }
+ set {
+ m_expression = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("{0}(", m_name);
+ if (m_expression != null)
+ {
+ bool first = true;
+ foreach (CssTerm t in m_expression.Terms)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else if (!t.Value.EndsWith("="))
+ {
+ sb.Append(", ");
+ }
+
+ bool quote = false;
+ if (t.Type == CssTermType.String && !t.Value.EndsWith("="))
+ {
+ quote = true;
+ }
+ if (quote)
+ {
+ sb.Append("'");
+ }
+ sb.Append(t.ToString());
+ if (quote)
+ {
+ sb.Append("'");
+ }
+ }
+ }
+ sb.Append(")");
+ return sb.ToString();
+ }
+ }
+
+ public interface ItfDeclarationContainer
+ {
+ List<CssDeclaration> Declarations { get; set; }
+ }
+
+ public interface ItfRuleSetContainer
+ {
+ List<CssRuleSet> RuleSets { get; set; }
+ }
+
+ public interface ItfSelectorContainer
+ {
+ List<CssSelector> Selectors { get; set; }
+ }
+
+ public enum CssMedium
+ {
+ all,
+ aural,
+ braille,
+ embossed,
+ handheld,
+ print,
+ projection,
+ screen,
+ tty,
+ tv
+ }
+
+ public class CssPropertyValue
+ {
+ private CssValueType m_type;
+ private CssUnit m_unit;
+ private string m_value;
+
+ public CssValueType Type
+ {
+ get {
+ return this.m_type;
+ }
+ set {
+ this.m_type = value;
+ }
+ }
+
+ public CssUnit Unit
+ {
+ get {
+ return this.m_unit;
+ }
+ set {
+ this.m_unit = value;
+ }
+ }
+
+ public string Value
+ {
+ get {
+ return this.m_value;
+ }
+ set {
+ this.m_value = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder(m_value);
+ if (m_type == CssValueType.Unit)
+ {
+ sb.Append(m_unit.ToString().ToLower());
+ }
+ sb.Append(" [");
+ sb.Append(m_type.ToString());
+ sb.Append("]");
+ return sb.ToString();
+ }
+
+ public bool IsColor
+ {
+ get
+ {
+ if (((m_type == CssValueType.Hex)
+ || (m_type == CssValueType.String && m_value.StartsWith("#")))
+ && (m_value.Length == 6 || (m_value.Length == 7 && m_value.StartsWith("#"))))
+ {
+ bool hex = true;
+ foreach (char c in m_value)
+ {
+ if (!char.IsDigit(c)
+ && c != '#'
+ && c != 'a'
+ && c != 'A'
+ && c != 'b'
+ && c != 'B'
+ && c != 'c'
+ && c != 'C'
+ && c != 'd'
+ && c != 'D'
+ && c != 'e'
+ && c != 'E'
+ && c != 'f'
+ && c != 'F'
+ )
+ {
+ return false;
+ }
+ }
+ return hex;
+ }
+ else if (m_type == CssValueType.String)
+ {
+ bool number = true;
+ foreach (char c in m_value)
+ {
+ if (!char.IsDigit(c))
+ {
+ number = false;
+ break;
+ }
+ }
+ if (number) { return false; }
+
+ try
+ {
+ KnownColor kc = (KnownColor)Enum.Parse(typeof(KnownColor), m_value, true);
+ return true;
+ }
+ catch { }
+ }
+ return false;
+ }
+ }
+
+ public Color ToColor()
+ {
+ string hex = "000000";
+ if (m_type == CssValueType.Hex)
+ {
+ if (m_value.Length == 7 && m_value.StartsWith("#"))
+ {
+ hex = m_value.Substring(1);
+ }
+ else if (m_value.Length == 6)
+ {
+ hex = m_value;
+ }
+ }
+ else
+ {
+ try
+ {
+ KnownColor kc = (KnownColor)Enum.Parse(typeof(KnownColor), m_value, true);
+ Color c = Color.FromKnownColor(kc);
+ return c;
+ }
+ catch { }
+ }
+ int r = ConvertFromHex(hex.Substring(0, 2));
+ int g = ConvertFromHex(hex.Substring(2, 2));
+ int b = ConvertFromHex(hex.Substring(4));
+ return Color.FromArgb(r, g, b);
+ }
+
+ private int ConvertFromHex(string input)
+ {
+ int val;
+ int result = 0;
+ for (int i = 0; i < input.Length; i++)
+ {
+ string chunk = input.Substring(i, 1).ToUpper();
+ switch (chunk)
+ {
+ case "A":
+ val = 10;
+ break;
+ case "B":
+ val = 11;
+ break;
+ case "C":
+ val = 12;
+ break;
+ case "D":
+ val = 13;
+ break;
+ case "E":
+ val = 14;
+ break;
+ case "F":
+ val = 15;
+ break;
+ default:
+ val = int.Parse(chunk);
+ break;
+ }
+ if (i == 0)
+ {
+ result += val * 16;
+ }
+ else
+ {
+ result += val;
+ }
+ }
+ return result;
+ }
+ }
+
+ public class CssRuleSet : ItfDeclarationContainer
+ {
+ private List<CssSelector> m_selectors = new List<CssSelector>();
+ private List<CssDeclaration> m_declarations = new List<CssDeclaration>();
+
+ public List<CssSelector> Selectors
+ {
+ get {
+ return m_selectors;
+ }
+ set {
+ m_selectors = value;
+ }
+ }
+
+ public List<CssDeclaration> Declarations
+ {
+ get {
+ return m_declarations;
+ }
+ set {
+ m_declarations = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ return ToString(0);
+ }
+
+ public string ToString(int indentLevel)
+ {
+ string start = "";
+ for (int i = 0; i < indentLevel; i++)
+ {
+ start += "\t";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ bool first = true;
+ foreach (CssSelector sel in m_selectors)
+ {
+ if (first)
+ {
+ first = false;
+ sb.Append(start);
+ }
+ else
+ {
+ sb.Append(", ");
+ }
+ sb.Append(sel.ToString());
+ }
+ sb.Append(" {" + Environment.NewLine);
+ sb.Append(start);
+
+ foreach (CssDeclaration dec in m_declarations)
+ {
+ sb.AppendFormat("\t{0};" + Environment.NewLine + "{1}", dec.ToString(), start);
+ }
+
+ sb.Append("}");
+ return sb.ToString();
+ }
+ }
+
+ public class CssSelector
+ {
+ private List<CssSimpleSelector> m_simpleSelectors = new List<CssSimpleSelector>();
+
+ public List<CssSimpleSelector> SimpleSelectors
+ {
+ get {
+ return m_simpleSelectors;
+ }
+ set {
+ m_simpleSelectors = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ bool first = true;
+ foreach (CssSimpleSelector ss in m_simpleSelectors)
+ {
+ if (first)
+ {
+ first = false;
+ }
+ else
+ {
+ sb.Append(" ");
+ }
+ sb.Append(ss.ToString());
+ }
+ return sb.ToString();
+ }
+ }
+
+ public class CssSimpleSelector
+ {
+ private CssCombinator? m_combinator = null;
+ private string m_elementname;
+ private string m_id;
+ private string m_cls;
+ private CssAttribute m_attribute;
+ private string m_pseudo;
+ private CssFunction m_function;
+ private CssSimpleSelector m_child;
+
+ public CssCombinator? Combinator
+ {
+ get {
+ return m_combinator;
+ }
+ set {
+ m_combinator = value;
+ }
+ }
+ public string CombinatorString
+ {
+ get {
+ if (this.m_combinator.HasValue)
+ {
+ return m_combinator.ToString();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ set {
+ this.m_combinator = (CssCombinator)Enum.Parse(typeof(CssCombinator), value);
+ }
+ }
+
+ public string ElementName
+ {
+ get {
+ return m_elementname;
+ }
+ set {
+ m_elementname = value;
+ }
+ }
+
+ public string ID
+ {
+ get {
+ return m_id;
+ }
+ set {
+ m_id = value;
+ }
+ }
+
+ public string Class
+ {
+ get {
+ return m_cls;
+ }
+ set {
+ m_cls = value;
+ }
+ }
+
+ public string Pseudo
+ {
+ get {
+ return m_pseudo;
+ }
+ set {
+ m_pseudo = value;
+ }
+ }
+
+ public CssAttribute Attribute
+ {
+ get {
+ return m_attribute;
+ }
+ set {
+ m_attribute = value;
+ }
+ }
+
+ public CssFunction Function
+ {
+ get {
+ return m_function;
+ }
+ set {
+ m_function = value;
+ }
+ }
+
+ public CssSimpleSelector Child
+ {
+ get {
+ return m_child;
+ }
+ set {
+ m_child = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ if (m_combinator.HasValue)
+ {
+ switch (m_combinator.Value)
+ {
+ case OpenXmlPowerTools.HtmlToWml.CSS.CssCombinator.PrecededImmediatelyBy:
+ sb.Append(" + ");
+ break;
+ case OpenXmlPowerTools.HtmlToWml.CSS.CssCombinator.ChildOf:
+ sb.Append(" > ");
+ break;
+ case OpenXmlPowerTools.HtmlToWml.CSS.CssCombinator.PrecededBy:
+ sb.Append(" ~ ");
+ break;
+ }
+ }
+ if (m_elementname != null)
+ {
+ sb.Append(m_elementname);
+ }
+ if (m_id != null)
+ {
+ sb.AppendFormat("#{0}", m_id);
+ }
+ if (m_cls != null)
+ {
+ sb.AppendFormat(".{0}", m_cls);
+ }
+ if (m_pseudo != null)
+ {
+ sb.AppendFormat(":{0}", m_pseudo);
+ }
+ if (m_attribute != null)
+ {
+ sb.Append(m_attribute.ToString());
+ }
+ if (m_function != null)
+ {
+ sb.Append(m_function.ToString());
+ }
+ if (m_child != null)
+ {
+ if (m_child.ElementName != null)
+ {
+ sb.Append(" ");
+ }
+ sb.Append(m_child.ToString());
+ }
+ return sb.ToString();
+ }
+ }
+
+ public class CssTag
+ {
+ private CssTagType m_tagtype;
+ private string m_name;
+ private string m_cls;
+ private string m_pseudo;
+ private string m_id;
+ private char m_parentrel = '\0';
+ private CssTag m_subtag;
+ private List<string> m_attribs = new List<string>();
+
+ public CssTagType TagType
+ {
+ get {
+ return m_tagtype;
+ }
+ set {
+ m_tagtype = value;
+ }
+ }
+
+ public bool IsIDSelector
+ {
+ get {
+ return m_id != null;
+ }
+ }
+
+ public bool HasName
+ {
+ get {
+ return m_name != null;
+ }
+ }
+
+ public bool HasClass
+ {
+ get {
+ return m_cls != null;
+ }
+ }
+
+ public bool HasPseudoClass
+ {
+ get {
+ return m_pseudo != null;
+ }
+ }
+
+ public string Name
+ {
+ get {
+ return m_name;
+ }
+ set {
+ m_name = value;
+ }
+ }
+
+ public string Class
+ {
+ get {
+ return m_cls;
+ }
+ set {
+ m_cls = value;
+ }
+ }
+
+ public string Pseudo
+ {
+ get {
+ return m_pseudo;
+ }
+ set {
+ m_pseudo = value;
+ }
+ }
+
+ public string Id
+ {
+ get {
+ return m_id;
+ }
+ set {
+ m_id = value;
+ }
+ }
+
+ public char ParentRelationship
+ {
+ get {
+ return m_parentrel;
+ }
+ set {
+ m_parentrel = value;
+ }
+ }
+
+ public CssTag SubTag
+ {
+ get {
+ return m_subtag;
+ }
+ set {
+ m_subtag = value;
+ }
+ }
+
+ public List<string> Attributes
+ {
+ get {
+ return m_attribs;
+ }
+ set {
+ m_attribs = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder(ToShortString());
+
+ if (m_subtag != null)
+ {
+ sb.Append(" ");
+ sb.Append(m_subtag.ToString());
+ }
+ return sb.ToString();
+ }
+
+ public string ToShortString()
+ {
+ StringBuilder sb = new StringBuilder();
+ if (m_parentrel != '\0')
+ {
+ sb.AppendFormat("{0} ", m_parentrel.ToString());
+ }
+ if (HasName)
+ {
+ sb.Append(m_name);
+ }
+ foreach (string atr in m_attribs)
+ {
+ sb.AppendFormat("[{0}]", atr);
+ }
+ if (HasClass)
+ {
+ sb.Append(".");
+ sb.Append(m_cls);
+ }
+ if (IsIDSelector)
+ {
+ sb.Append("#");
+ sb.Append(m_id);
+ }
+ if (HasPseudoClass)
+ {
+ sb.Append(":");
+ sb.Append(m_pseudo);
+ }
+ return sb.ToString();
+ }
+ }
+
+ [Flags]
+ public enum CssTagType
+ {
+ Named = 1,
+ Classed = 2,
+ IDed = 4,
+ Pseudoed = 8,
+ Directive = 16
+ }
+
+ public class CssTerm
+ {
+ private char? m_separator;
+ private char? m_sign;
+ private CssTermType m_type;
+ private string m_val;
+ private CssUnit? m_unit;
+ private CssFunction m_function;
+
+ public char? Separator
+ {
+ get {
+ return m_separator;
+ }
+ set {
+ m_separator = value;
+ }
+ }
+ public string SeparatorChar
+ {
+ get {
+ return m_separator.HasValue ? this.m_separator.Value.ToString() : null;
+ }
+ set {
+ m_separator = !string.IsNullOrEmpty(value) ? value[0] : '\0';
+ }
+ }
+
+ public char? Sign
+ {
+ get {
+ return m_sign;
+ }
+ set {
+ m_sign = value;
+ }
+ }
+ public string SignChar
+ {
+ get {
+ return this.m_sign.HasValue ? this.m_sign.Value.ToString() : null;
+ }
+ set {
+ this.m_sign = !string.IsNullOrEmpty(value) ? value[0] : '\0';
+ }
+ }
+
+ public CssTermType Type
+ {
+ get {
+ return m_type;
+ }
+ set {
+ m_type = value;
+ }
+ }
+
+ public string Value
+ {
+ get {
+ return m_val;
+ }
+ set {
+ m_val = value;
+ }
+ }
+
+ public CssUnit? Unit
+ {
+ get {
+ return m_unit;
+ }
+ set {
+ m_unit = value;
+ }
+ }
+ public string UnitString
+ {
+ get {
+ if (this.m_unit.HasValue)
+ {
+ return this.m_unit.ToString();
+ }
+ else
+ {
+ return null;
+ }
+ }
+ set {
+ this.m_unit = (CssUnit)Enum.Parse(typeof(CssUnit), value);
+ }
+ }
+
+ public CssFunction Function
+ {
+ get {
+ return m_function;
+ }
+ set {
+ m_function = value;
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ if (m_type == CssTermType.Function)
+ {
+ sb.Append(m_function.ToString());
+ }
+ else if (m_type == CssTermType.Url)
+ {
+ sb.AppendFormat("url('{0}')", m_val);
+ }
+ else if (m_type == CssTermType.Unicode)
+ {
+ sb.AppendFormat("U\\{0}", m_val.ToUpper());
+ }
+ else if (m_type == CssTermType.Hex)
+ {
+ sb.Append(m_val.ToUpper());
+ }
+ else
+ {
+ if (m_sign.HasValue)
+ {
+ sb.Append(m_sign.Value);
+ }
+ sb.Append(m_val);
+ if (m_unit.HasValue)
+ {
+ if (m_unit.Value == OpenXmlPowerTools.HtmlToWml.CSS.CssUnit.Percent)
+ {
+ sb.Append("%");
+ }
+ else
+ {
+ sb.Append(CssUnitOutput.ToString(m_unit.Value));
+ }
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public bool IsColor
+ {
+ get
+ {
+ if (((m_type == CssTermType.Hex)
+ || (m_type == CssTermType.String && m_val.StartsWith("#")))
+ && (m_val.Length == 6 || m_val.Length == 3 || ((m_val.Length == 7 || m_val.Length == 4)
+ && m_val.StartsWith("#"))))
+ {
+ bool hex = true;
+ foreach (char c in m_val)
+ {
+ if (!char.IsDigit(c)
+ && c != '#'
+ && c != 'a'
+ && c != 'A'
+ && c != 'b'
+ && c != 'B'
+ && c != 'c'
+ && c != 'C'
+ && c != 'd'
+ && c != 'D'
+ && c != 'e'
+ && c != 'E'
+ && c != 'f'
+ && c != 'F'
+ )
+ {
+ return false;
+ }
+ }
+ return hex;
+ }
+ else if (m_type == CssTermType.String)
+ {
+ bool number = true;
+ foreach (char c in m_val)
+ {
+ if (!char.IsDigit(c))
+ {
+ number = false;
+ break;
+ }
+ }
+ if (number) {
+ return false;
+ }
+
+ KnownColor kc;
+ if (Enum.TryParse(m_val, true, out kc))
+ {
+ return true;
+ }
+ }
+ else if (m_type == CssTermType.Function)
+ {
+ if ((m_function.Name.ToLower().Equals("rgb") && m_function.Expression.Terms.Count == 3)
+ || (m_function.Name.ToLower().Equals("rgba") && m_function.Expression.Terms.Count == 4)
+ )
+ {
+ for (int i = 0; i < m_function.Expression.Terms.Count; i++)
+ {
+ if (m_function.Expression.Terms[i].Type != CssTermType.Number)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else if ((m_function.Name.ToLower().Equals("hsl") && m_function.Expression.Terms.Count == 3)
+ || (m_function.Name.ToLower().Equals("hsla") && m_function.Expression.Terms.Count == 4)
+ )
+ {
+ for (int i = 0; i < m_function.Expression.Terms.Count; i++)
+ {
+ if (m_function.Expression.Terms[i].Type != CssTermType.Number)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private int GetRGBValue(CssTerm t)
+ {
+ try
+ {
+ if (t.Unit.HasValue && t.Unit.Value == OpenXmlPowerTools.HtmlToWml.CSS.CssUnit.Percent)
+ {
+ return (int)(255f * float.Parse(t.Value) / 100f);
+ }
+ return int.Parse(t.Value);
+ }
+ catch { }
+ return 0;
+ }
+
+ private int GetHueValue(CssTerm t)
+ {
+ try
+ {
+ return (int)(float.Parse(t.Value) * 255f / 360f);
+ }
+ catch { }
+ return 0;
+ }
+
+ public Color ToColor()
+ {
+ string hex = "000000";
+ if (m_type == CssTermType.Hex)
+ {
+ if ((m_val.Length == 7 || m_val.Length == 4) && m_val.StartsWith("#"))
+ {
+ hex = m_val.Substring(1);
+ }
+ else if (m_val.Length == 6 || m_val.Length == 3)
+ {
+ hex = m_val;
+ }
+ }
+ else if (m_type == CssTermType.Function)
+ {
+ if ((m_function.Name.ToLower().Equals("rgb") && m_function.Expression.Terms.Count == 3)
+ || (m_function.Name.ToLower().Equals("rgba") && m_function.Expression.Terms.Count == 4)
+ )
+ {
+ int fr = 0, fg = 0, fb = 0;
+ for (int i = 0; i < m_function.Expression.Terms.Count; i++)
+ {
+ if (m_function.Expression.Terms[i].Type != CssTermType.Number)
+ {
+ return Color.Black;
+ }
+ switch (i)
+ {
+ case 0: fr = GetRGBValue(m_function.Expression.Terms[i]);
+ break;
+ case 1: fg = GetRGBValue(m_function.Expression.Terms[i]);
+ break;
+ case 2: fb = GetRGBValue(m_function.Expression.Terms[i]);
+ break;
+ }
+ }
+ return Color.FromArgb(fr, fg, fb);
+ }
+ else if ((m_function.Name.ToLower().Equals("hsl") && m_function.Expression.Terms.Count == 3)
+ || (m_function.Name.Equals("hsla") && m_function.Expression.Terms.Count == 4)
+ )
+ {
+ int h = 0, s = 0, v = 0;
+ for (int i = 0; i < m_function.Expression.Terms.Count; i++)
+ {
+ if (m_function.Expression.Terms[i].Type != CssTermType.Number) { return Color.Black; }
+ switch (i)
+ {
+ case 0: h = GetHueValue(m_function.Expression.Terms[i]);
+ break;
+ case 1: s = GetRGBValue(m_function.Expression.Terms[i]);
+ break;
+ case 2: v = GetRGBValue(m_function.Expression.Terms[i]);
+ break;
+ }
+ }
+ HueSatVal hsv = new HueSatVal(h, s, v);
+ return hsv.Color;
+ }
+ }
+ else
+ {
+ try
+ {
+ KnownColor kc = (KnownColor)Enum.Parse(typeof(KnownColor), m_val, true);
+ Color c = Color.FromKnownColor(kc);
+ return c;
+ }
+ catch { }
+ }
+ if (hex.Length == 3)
+ {
+ string temp = "";
+ foreach (char c in hex)
+ {
+ temp += c.ToString() + c.ToString();
+ }
+ hex = temp;
+ }
+ int r = ConvertFromHex(hex.Substring(0, 2));
+ int g = ConvertFromHex(hex.Substring(2, 2));
+ int b = ConvertFromHex(hex.Substring(4));
+ return Color.FromArgb(r, g, b);
+ }
+ private int ConvertFromHex(string input)
+ {
+ int val;
+ int result = 0;
+ for (int i = 0; i < input.Length; i++)
+ {
+ string chunk = input.Substring(i, 1).ToUpper();
+ switch (chunk)
+ {
+ case "A":
+ val = 10;
+ break;
+ case "B":
+ val = 11;
+ break;
+ case "C":
+ val = 12;
+ break;
+ case "D":
+ val = 13;
+ break;
+ case "E":
+ val = 14;
+ break;
+ case "F":
+ val = 15;
+ break;
+ default:
+ val = int.Parse(chunk);
+ break;
+ }
+ if (i == 0)
+ {
+ result += val * 16;
+ }
+ else
+ {
+ result += val;
+ }
+ }
+ return result;
+ }
+ }
+
+ public enum CssTermType
+ {
+ Number,
+ Function,
+ String,
+ Url,
+ Unicode,
+ Hex
+ }
+
+ public enum CssUnit
+ {
+ None,
+ Percent,
+ EM,
+ EX,
+ PX,
+ GD,
+ REM,
+ VW,
+ VH,
+ VM,
+ CH,
+ MM,
+ CM,
+ IN,
+ PT,
+ PC,
+ DEG,
+ GRAD,
+ RAD,
+ TURN,
+ MS,
+ S,
+ Hz,
+ kHz,
+ }
+
+ public static class CssUnitOutput
+ {
+ public static string ToString(CssUnit u)
+ {
+ if (u == CssUnit.Percent)
+ {
+ return "%";
+ }
+ else if (u == CssUnit.Hz || u == CssUnit.kHz)
+ {
+ return u.ToString();
+ }
+ else if (u == CssUnit.None)
+ {
+ return "";
+ }
+ return u.ToString().ToLower();
+ }
+ }
+
+ public enum CssValueType
+ {
+ String,
+ Hex,
+ Unit,
+ Percent,
+ Url,
+ Function
+ }
+
+ public class CssParser
+ {
+ private List<string> m_errors = new List<string>();
+ private CssDocument m_doc;
+
+ public CssDocument ParseText(string content)
+ {
+ MemoryStream mem = new MemoryStream();
+ byte[] bytes = ASCIIEncoding.ASCII.GetBytes(content);
+ mem.Write(bytes, 0, bytes.Length);
+ try
+ {
+ return ParseStream(mem);
+ }
+ catch (OpenXmlPowerToolsException e)
+ {
+ string msg = e.Message + ". CSS => " + content;
+ throw new OpenXmlPowerToolsException(msg);
+ }
+ }
+
+ // following method should be private, as it does not properly re-throw OpenXmlPowerToolsException
+ private CssDocument ParseStream(Stream stream)
+ {
+ Scanner scanner = new Scanner(stream);
+ Parser parser = new Parser(scanner);
+ parser.Parse();
+ m_doc = parser.CssDoc;
+ return m_doc;
+ }
+
+ public CssDocument CSSDocument
+ {
+ get { return m_doc; }
+ }
+
+ public List<string> Errors
+ {
+ get { return m_errors; }
+ }
+ }
+
+ // Hue Sat and Val values from 0 - 255.
+ internal struct HueSatVal
+ {
+ private int m_hue;
+ private int m_sat;
+ private int m_val;
+ public HueSatVal(int h, int s, int v)
+ {
+ m_hue = h;
+ m_sat = s;
+ m_val = v;
+ }
+ public HueSatVal(Color color)
+ {
+ m_hue = 0;
+ m_sat = 0;
+ m_val = 0;
+ ConvertFromRGB(color);
+ }
+ public int Hue
+ {
+ get {
+ return m_hue;
+ }
+ set {
+ m_hue = value;
+ }
+ }
+ public int Saturation
+ {
+ get {
+ return m_sat;
+ }
+ set {
+ m_sat = value;
+ }
+ }
+ public int Value
+ {
+ get {
+ return m_val;
+ }
+ set {
+ m_val = value;
+ }
+ }
+ public Color Color
+ {
+ get {
+ return ConvertToRGB();
+ }
+ set {
+ ConvertFromRGB(value);
+ }
+ }
+ private void ConvertFromRGB(Color color)
+ {
+ double min; double max; double delta;
+ double r = (double)color.R / 255.0d;
+ double g = (double)color.G / 255.0d;
+ double b = (double)color.B / 255.0d;
+ double h; double s; double v;
+
+ min = Math.Min(Math.Min(r, g), b);
+ max = Math.Max(Math.Max(r, g), b);
+ v = max;
+ delta = max - min;
+ if (max == 0 || delta == 0)
+ {
+ s = 0;
+ h = 0;
+ }
+ else
+ {
+ s = delta / max;
+ if (r == max)
+ {
+ h = (60D * ((g - b) / delta)) % 360.0d;
+ }
+ else if (g == max)
+ {
+ h = 60D * ((b - r) / delta) + 120.0d;
+ }
+ else
+ {
+ h = 60D * ((r - g) / delta) + 240.0d;
+ }
+ }
+ if (h < 0)
+ {
+ h += 360.0d;
+ }
+
+ Hue = (int)(h / 360.0d * 255.0d);
+ Saturation = (int)(s * 255.0d);
+ Value = (int)(v * 255.0d);
+ }
+
+ private Color ConvertToRGB()
+ {
+ double h;
+ double s;
+ double v;
+ double r = 0;
+ double g = 0;
+ double b = 0;
+
+ h = ((double)Hue / 255.0d * 360.0d) % 360.0d;
+ s = (double)Saturation / 255.0d;
+ v = (double)Value / 255.0d;
+
+ if (s == 0)
+ {
+ r = v;
+ g = v;
+ b = v;
+ }
+ else
+ {
+ double p;
+ double q;
+ double t;
+
+ double fractionalPart;
+ int sectorNumber;
+ double sectorPos;
+
+ sectorPos = h / 60.0d;
+ sectorNumber = (int)(Math.Floor(sectorPos));
+
+ fractionalPart = sectorPos - sectorNumber;
+
+ p = v * (1.0d - s);
+ q = v * (1.0d - (s * fractionalPart));
+ t = v * (1.0d - (s * (1.0d - fractionalPart)));
+
+ switch (sectorNumber)
+ {
+ case 0:
+ r = v;
+ g = t;
+ b = p;
+ break;
+ case 1:
+ r = q;
+ g = v;
+ b = p;
+ break;
+ case 2:
+ r = p;
+ g = v;
+ b = t;
+ break;
+ case 3:
+ r = p;
+ g = q;
+ b = v;
+ break;
+ case 4:
+ r = t;
+ g = p;
+ b = v;
+ break;
+ case 5:
+ r = v;
+ g = p;
+ b = q;
+ break;
+ }
+ }
+ return Color.FromArgb((int)(r * 255.0d), (int)(g * 255.0d), (int)(b * 255.0d));
+ }
+
+ public static bool operator !=(HueSatVal left, HueSatVal right)
+ {
+ return !(left == right);
+ }
+
+ public static bool operator ==(HueSatVal left, HueSatVal right)
+ {
+ return (left.Hue == right.Hue && left.Value == right.Value && left.Saturation == right.Saturation);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return this == (HueSatVal)obj;
+ }
+
+ public override int GetHashCode()
+ {
+ return base.GetHashCode();
+ }
+ }
+
+ public class Parser
+ {
+ public const int c_EOF = 0;
+ public const int c_ident = 1;
+ public const int c_newline = 2;
+ public const int c_digit = 3;
+ public const int c_whitespace = 4;
+ public const int c_maxT = 49;
+
+ const bool T = true;
+ const bool x = false;
+ const int minErrDist = 2;
+
+ public Scanner m_scanner;
+ public Errors m_errors;
+
+ public CssToken m_lastRecognizedToken;
+ public CssToken m_lookaheadToken;
+ int errDist = minErrDist;
+
+ public CssDocument CssDoc;
+
+ bool IsInHex(string value)
+ {
+ if (value.Length == 7)
+ {
+ return false;
+ }
+ if (value.Length + m_lookaheadToken.m_tokenValue.Length > 7)
+ {
+ return false;
+ }
+ var hexes = new List<string>
+ {
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "a", "b", "c", "d", "e", "f"
+ };
+ foreach (char c in m_lookaheadToken.m_tokenValue)
+ {
+ if (!hexes.Contains(c.ToString()))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool IsUnitOfLength()
+ {
+ if (m_lookaheadToken.m_tokenKind != 1)
+ {
+ return false;
+ }
+ System.Collections.Generic.List<string> units = new System.Collections.Generic.List<string>(
+ new string[]
+ {
+ "em", "ex", "px", "gd", "rem", "vw", "vh", "vm", "ch", "mm", "cm", "in", "pt", "pc", "deg", "grad", "rad", "turn", "ms", "s", "hz", "khz"
+ });
+ return units.Contains(m_lookaheadToken.m_tokenValue.ToLower());
+ }
+
+ bool IsNumber()
+ {
+ if (m_lookaheadToken.m_tokenValue.Length > 0)
+ {
+ return char.IsDigit(m_lookaheadToken.m_tokenValue[0]);
+ }
+ return false;
+ }
+
+ public Parser(Scanner scanner)
+ {
+ this.m_scanner = scanner;
+ m_errors = new Errors();
+ }
+
+ void SyntaxErr(int n)
+ {
+ if (errDist >= minErrDist)
+ m_errors.SyntaxError(m_lookaheadToken.m_tokenLine, m_lookaheadToken.m_tokenColumn, n);
+ errDist = 0;
+ }
+
+ public void SemanticErr(string msg)
+ {
+ if (errDist >= minErrDist)
+ m_errors.SemanticError(m_lastRecognizedToken.m_tokenLine, m_lastRecognizedToken.m_tokenColumn, msg);
+ errDist = 0;
+ }
+
+ void Get()
+ {
+ for (;;)
+ {
+ m_lastRecognizedToken = m_lookaheadToken;
+ m_lookaheadToken = m_scanner.Scan();
+ if (m_lookaheadToken.m_tokenKind <= c_maxT)
+ {
+ ++errDist;
+ break;
+ }
+
+ m_lookaheadToken = m_lastRecognizedToken;
+ }
+ }
+
+ void Expect(int n)
+ {
+ if (m_lookaheadToken.m_tokenKind == n)
+ Get();
+ else
+ {
+ SyntaxErr(n);
+ }
+ }
+
+ bool StartOf(int s)
+ {
+ return set[s, m_lookaheadToken.m_tokenKind];
+ }
+
+ void ExpectWeak(int n, int follow)
+ {
+ if (m_lookaheadToken.m_tokenKind == n)
+ Get();
+ else
+ {
+ SyntaxErr(n);
+ while (!StartOf(follow))
+ Get();
+ }
+ }
+
+
+ bool WeakSeparator(int n, int syFol, int repFol)
+ {
+ int kind = m_lookaheadToken.m_tokenKind;
+ if (kind == n)
+ {
+ Get();
+ return true;
+ }
+ else if (StartOf(repFol))
+ {
+ return false;
+ }
+ else
+ {
+ SyntaxErr(n);
+ while (!(set[syFol, kind] || set[repFol, kind] || set[0, kind]))
+ {
+ Get();
+ kind = m_lookaheadToken.m_tokenKind;
+ }
+ return StartOf(syFol);
+ }
+ }
+
+
+ void Css3()
+ {
+ CssDoc = new CssDocument();
+ CssRuleSet rset = null;
+ CssDirective dir = null;
+
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 5 || m_lookaheadToken.m_tokenKind == 6)
+ {
+ if (m_lookaheadToken.m_tokenKind == 5)
+ {
+ Get();
+ }
+ else
+ {
+ Get();
+ }
+ }
+ while (StartOf(1))
+ {
+ if (StartOf(2))
+ {
+ RuleSet(out rset);
+ CssDoc.RuleSets.Add(rset);
+ }
+ else
+ {
+ Directive(out dir);
+ CssDoc.Directives.Add(dir);
+ }
+ while (m_lookaheadToken.m_tokenKind == 5 || m_lookaheadToken.m_tokenKind == 6)
+ {
+ if (m_lookaheadToken.m_tokenKind == 5)
+ {
+ Get();
+ }
+ else
+ {
+ Get();
+ }
+ }
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+
+ void RuleSet(out CssRuleSet rset)
+ {
+ rset = new CssRuleSet();
+ CssSelector sel = null;
+ CssDeclaration dec = null;
+
+ Selector(out sel);
+ rset.Selectors.Add(sel);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 25)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Selector(out sel);
+ rset.Selectors.Add(sel);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ Expect(26);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (StartOf(3))
+ {
+ Declaration(out dec);
+ rset.Declarations.Add(dec);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 27)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenValue.Equals("}"))
+ {
+ Get();
+ return;
+ }
+
+ Declaration(out dec);
+ rset.Declarations.Add(dec);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ if (m_lookaheadToken.m_tokenKind == 27)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+ Expect(28);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+
+ void Directive(out CssDirective dir)
+ {
+ dir = new CssDirective();
+ CssDeclaration dec = null;
+ CssRuleSet rset = null;
+ CssExpression exp = null;
+ CssDirective dr = null;
+ string ident = null;
+ CssMedium m;
+
+ Expect(23);
+ dir.Name = "@";
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ dir.Name += "-";
+ }
+ Identity(out ident);
+ dir.Name += ident;
+ switch (dir.Name.ToLower())
+ {
+ case "@media":
+ dir.Type = CssDirectiveType.Media;
+ break;
+ case "@import":
+ dir.Type = CssDirectiveType.Import;
+ break;
+ case "@charset":
+ dir.Type = CssDirectiveType.Charset;
+ break;
+ case "@page":
+ dir.Type = CssDirectiveType.Page;
+ break;
+ case "@font-face":
+ dir.Type = CssDirectiveType.FontFace;
+ break;
+ case "@namespace":
+ dir.Type = CssDirectiveType.Namespace;
+ break;
+ default:
+ dir.Type = CssDirectiveType.Other;
+ break;
+ }
+
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (StartOf(4))
+ {
+ if (StartOf(5))
+ {
+ Medium(out m);
+ dir.Mediums.Add(m);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 25)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Medium(out m);
+ dir.Mediums.Add(m);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+ else
+ {
+ Exprsn(out exp);
+ dir.Expression = exp;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+ if (m_lookaheadToken.m_tokenKind == 26)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (StartOf(6))
+ {
+ while (StartOf(1))
+ {
+ if (dir.Type == CssDirectiveType.Page || dir.Type == CssDirectiveType.FontFace)
+ {
+ Declaration(out dec);
+ dir.Declarations.Add(dec);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 27)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenValue.Equals("}"))
+ {
+ Get();
+ return;
+ }
+ Declaration(out dec);
+ dir.Declarations.Add(dec);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ if (m_lookaheadToken.m_tokenKind == 27)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+ else if (StartOf(2))
+ {
+ RuleSet(out rset);
+ dir.RuleSets.Add(rset);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ else
+ {
+ Directive(out dr);
+ dir.Directives.Add(dr);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+ }
+ Expect(28);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ else if (m_lookaheadToken.m_tokenKind == 27)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ else SyntaxErr(50);
+ }
+
+ void QuotedString(out string qs)
+ {
+ qs = "";
+ if (m_lookaheadToken.m_tokenKind == 7)
+ {
+ Get();
+ while (StartOf(7))
+ {
+ Get();
+ qs += m_lastRecognizedToken.m_tokenValue;
+ if (m_lookaheadToken.m_tokenValue.Equals("'") && !m_lastRecognizedToken.m_tokenValue.Equals("\\"))
+ {
+ break;
+ }
+ }
+ Expect(7);
+ }
+ else if (m_lookaheadToken.m_tokenKind == 8)
+ {
+ Get();
+ while (StartOf(8))
+ {
+ Get();
+ qs += m_lastRecognizedToken.m_tokenValue;
+ if (m_lookaheadToken.m_tokenValue.Equals("\"") && !m_lastRecognizedToken.m_tokenValue.Equals("\\"))
+ {
+ break;
+ }
+ }
+ Expect(8);
+ }
+ else SyntaxErr(51);
+
+ }
+
+ void URI(out string url)
+ {
+ url = "";
+ Expect(9);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenKind == 10)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenKind == 7 || m_lookaheadToken.m_tokenKind == 8)
+ {
+ QuotedString(out url);
+ }
+ else if (StartOf(9))
+ {
+ while (StartOf(10))
+ {
+ Get();
+ url += m_lastRecognizedToken.m_tokenValue;
+ if (m_lookaheadToken.m_tokenValue.Equals(")"))
+ {
+ break;
+ }
+ }
+ }
+ else SyntaxErr(52);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenKind == 11)
+ {
+ Get();
+ }
+ }
+
+ void Medium(out CssMedium m)
+ {
+ m = CssMedium.all;
+ switch (m_lookaheadToken.m_tokenKind)
+ {
+ case 12:
+ {
+ Get();
+ m = CssMedium.all;
+ break;
+ }
+ case 13:
+ {
+ Get();
+ m = CssMedium.aural;
+ break;
+ }
+ case 14:
+ {
+ Get();
+ m = CssMedium.braille;
+ break;
+ }
+ case 15:
+ {
+ Get();
+ m = CssMedium.embossed;
+ break;
+ }
+ case 16:
+ {
+ Get();
+ m = CssMedium.handheld;
+ break;
+ }
+ case 17:
+ {
+ Get();
+ m = CssMedium.print;
+ break;
+ }
+ case 18:
+ {
+ Get();
+ m = CssMedium.projection;
+ break;
+ }
+ case 19:
+ {
+ Get();
+ m = CssMedium.screen;
+ break;
+ }
+ case 20:
+ {
+ Get();
+ m = CssMedium.tty;
+ break;
+ }
+ case 21:
+ {
+ Get();
+ m = CssMedium.tv;
+ break;
+ }
+ default: SyntaxErr(53); break;
+ }
+ }
+
+ void Identity(out string ident)
+ {
+ ident = "";
+ switch (m_lookaheadToken.m_tokenKind)
+ {
+ case 1:
+ {
+ Get();
+ break;
+ }
+ case 22:
+ {
+ Get();
+ break;
+ }
+ case 9:
+ {
+ Get();
+ break;
+ }
+ case 12:
+ {
+ Get();
+ break;
+ }
+ case 13:
+ {
+ Get();
+ break;
+ }
+ case 14:
+ {
+ Get();
+ break;
+ }
+ case 15:
+ {
+ Get();
+ break;
+ }
+ case 16:
+ {
+ Get();
+ break;
+ }
+ case 17:
+ {
+ Get();
+ break;
+ }
+ case 18:
+ {
+ Get();
+ break;
+ }
+ case 19:
+ {
+ Get();
+ break;
+ }
+ case 20:
+ {
+ Get();
+ break;
+ }
+ case 21:
+ {
+ Get();
+ break;
+ }
+ default: SyntaxErr(54); break;
+ }
+ ident += m_lastRecognizedToken.m_tokenValue;
+ }
+
+ void Exprsn(out CssExpression exp)
+ {
+ exp = new CssExpression();
+ char? sep = null;
+ CssTerm trm = null;
+
+ Term(out trm);
+ exp.Terms.Add(trm);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (StartOf(11))
+ {
+ if (m_lookaheadToken.m_tokenKind == 25 || m_lookaheadToken.m_tokenKind == 46)
+ {
+ if (m_lookaheadToken.m_tokenKind == 46)
+ {
+ Get();
+ sep = '/';
+ }
+ else
+ {
+ Get();
+ sep = ',';
+ }
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ Term(out trm);
+ if (sep.HasValue)
+ {
+ trm.Separator = sep.Value;
+ }
+ exp.Terms.Add(trm);
+ sep = null;
+
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+
+ void Declaration(out CssDeclaration dec)
+ {
+ dec = new CssDeclaration();
+ CssExpression exp = null;
+ string ident = "";
+
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ dec.Name += "-";
+ }
+ Identity(out ident);
+ dec.Name += ident;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Expect(43);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Exprsn(out exp);
+ dec.Expression = exp;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenKind == 44)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Expect(45);
+ dec.Important = true;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+
+ void Selector(out CssSelector sel)
+ {
+ sel = new CssSelector();
+ CssSimpleSelector ss = null;
+ CssCombinator? cb = null;
+
+ SimpleSelector(out ss);
+ sel.SimpleSelectors.Add(ss);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ while (StartOf(12))
+ {
+ if (m_lookaheadToken.m_tokenKind == 29 || m_lookaheadToken.m_tokenKind == 30 || m_lookaheadToken.m_tokenKind == 31)
+ {
+ if (m_lookaheadToken.m_tokenKind == 29)
+ {
+ Get();
+ cb = CssCombinator.PrecededImmediatelyBy;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 30)
+ {
+ Get();
+ cb = CssCombinator.ChildOf;
+ }
+ else
+ {
+ Get();
+ cb = CssCombinator.PrecededBy;
+ }
+ }
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ SimpleSelector(out ss);
+ if (cb.HasValue)
+ {
+ ss.Combinator = cb.Value;
+ }
+ sel.SimpleSelectors.Add(ss);
+
+ cb = null;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ }
+
+ void SimpleSelector(out CssSimpleSelector ss)
+ {
+ ss = new CssSimpleSelector();
+ ss.ElementName = "";
+ string psd = null;
+ OpenXmlPowerTools.HtmlToWml.CSS.CssAttribute atb = null;
+ CssSimpleSelector parent = ss;
+ string ident = null;
+
+ if (StartOf(3))
+ {
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ ss.ElementName += "-";
+ }
+ Identity(out ident);
+ ss.ElementName += ident;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 32)
+ {
+ Get();
+ ss.ElementName = "*";
+ }
+ else if (StartOf(13))
+ {
+ if (m_lookaheadToken.m_tokenKind == 33)
+ {
+ Get();
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ ss.ID = "-";
+ }
+ Identity(out ident);
+ if (ss.ID == null)
+ {
+ ss.ID = ident;
+ }
+ else
+ {
+ ss.ID += ident;
+ }
+ }
+ else if (m_lookaheadToken.m_tokenKind == 34)
+ {
+ Get();
+ ss.Class = "";
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ ss.Class += "-";
+ }
+ Identity(out ident);
+ ss.Class += ident;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 35)
+ {
+ Attrib(out atb);
+ ss.Attribute = atb;
+ }
+ else
+ {
+ Pseudo(out psd);
+ ss.Pseudo = psd;
+ }
+ }
+ else SyntaxErr(55);
+ while (StartOf(13))
+ {
+ CssSimpleSelector child = new CssSimpleSelector();
+ if (m_lookaheadToken.m_tokenKind == 33)
+ {
+ Get();
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ child.ID = "-";
+ }
+ Identity(out ident);
+ if (child.ID == null)
+ {
+ child.ID = ident;
+ }
+ else
+ {
+ child.ID += "-";
+ }
+ }
+ else if (m_lookaheadToken.m_tokenKind == 34)
+ {
+ Get();
+ child.Class = "";
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ child.Class += "-";
+ }
+ Identity(out ident);
+ child.Class += ident;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 35)
+ {
+ Attrib(out atb);
+ child.Attribute = atb;
+ }
+ else
+ {
+ Pseudo(out psd);
+ child.Pseudo = psd;
+ }
+ parent.Child = child;
+ parent = child;
+
+ }
+ }
+
+ void Attrib(out OpenXmlPowerTools.HtmlToWml.CSS.CssAttribute atb)
+ {
+ atb = new OpenXmlPowerTools.HtmlToWml.CSS.CssAttribute();
+ atb.Value = "";
+ string quote = null;
+ string ident = null;
+
+ Expect(35);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Identity(out ident);
+ atb.Operand = ident;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (StartOf(14))
+ {
+ switch (m_lookaheadToken.m_tokenKind)
+ {
+ case 36:
+ {
+ Get();
+ atb.Operator = CssAttributeOperator.Equals;
+ break;
+ }
+ case 37:
+ {
+ Get();
+ atb.Operator = CssAttributeOperator.InList;
+ break;
+ }
+ case 38:
+ {
+ Get();
+ atb.Operator = CssAttributeOperator.Hyphenated;
+ break;
+ }
+ case 39:
+ {
+ Get();
+ atb.Operator = CssAttributeOperator.EndsWith;
+ break;
+ }
+ case 40:
+ {
+ Get();
+ atb.Operator = CssAttributeOperator.BeginsWith;
+ break;
+ }
+ case 41:
+ {
+ Get();
+ atb.Operator = CssAttributeOperator.Contains;
+ break;
+ }
+ }
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (StartOf(3))
+ {
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ atb.Value += "-";
+ }
+ Identity(out ident);
+ atb.Value += ident;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 7 || m_lookaheadToken.m_tokenKind == 8)
+ {
+ QuotedString(out quote);
+ atb.Value = quote;
+ }
+ else SyntaxErr(56);
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ }
+ Expect(42);
+ }
+
+ void Pseudo(out string pseudo)
+ {
+ pseudo = "";
+ CssExpression exp = null;
+ string ident = null;
+
+ Expect(43);
+ if (m_lookaheadToken.m_tokenKind == 43)
+ {
+ Get();
+ }
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ pseudo += "-";
+ }
+ Identity(out ident);
+ pseudo += ident;
+ if (m_lookaheadToken.m_tokenKind == 10)
+ {
+ Get();
+ pseudo += m_lastRecognizedToken.m_tokenValue;
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Exprsn(out exp);
+ pseudo += exp.ToString();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Expect(11);
+ pseudo += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+
+ void Term(out CssTerm trm)
+ {
+ trm = new CssTerm();
+ string val = "";
+ CssExpression exp = null;
+ string ident = null;
+
+ if (m_lookaheadToken.m_tokenKind == 7 || m_lookaheadToken.m_tokenKind == 8)
+ {
+ QuotedString(out val);
+ trm.Value = val; trm.Type = CssTermType.String;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 9)
+ {
+ URI(out val);
+ trm.Value = val;
+ trm.Type = CssTermType.Url;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 47)
+ {
+ Get();
+ Identity(out ident);
+ trm.Value = "U\\" + ident;
+ trm.Type = CssTermType.Unicode;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 33)
+ {
+ HexValue(out val);
+ trm.Value = val;
+ trm.Type = CssTermType.Hex;
+ }
+ else if (StartOf(15))
+ {
+ bool minus = false;
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ minus = true;
+ }
+ if (StartOf(16))
+ {
+ Identity(out ident);
+ trm.Value = ident;
+ trm.Type = CssTermType.String;
+ if (minus)
+ {
+ trm.Value = "-" + trm.Value;
+ }
+ if (StartOf(17))
+ {
+ while (m_lookaheadToken.m_tokenKind == 34 || m_lookaheadToken.m_tokenKind == 36 || m_lookaheadToken.m_tokenKind == 43)
+ {
+ if (m_lookaheadToken.m_tokenKind == 43)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ if (StartOf(18))
+ {
+ if (m_lookaheadToken.m_tokenKind == 43)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ Identity(out ident);
+ trm.Value += ident;
+ }
+ else if (m_lookaheadToken.m_tokenKind == 33)
+ {
+ HexValue(out val);
+ trm.Value += val;
+ }
+ else if (StartOf(19))
+ {
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ if (m_lookaheadToken.m_tokenKind == 34)
+ {
+ Get();
+ trm.Value += ".";
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+ }
+ else SyntaxErr(57);
+ }
+ else if (m_lookaheadToken.m_tokenKind == 34)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ Identity(out ident);
+ trm.Value += ident;
+ }
+ else
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ if (m_lookaheadToken.m_tokenKind == 24)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ if (StartOf(16))
+ {
+ Identity(out ident);
+ trm.Value += ident;
+ }
+ else if (StartOf(19))
+ {
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ trm.Value += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+ else SyntaxErr(58);
+ }
+ }
+ }
+ if (m_lookaheadToken.m_tokenKind == 10)
+ {
+ Get();
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Exprsn(out exp);
+ CssFunction func = new CssFunction();
+ func.Name = trm.Value;
+ func.Expression = exp;
+ trm.Value = null;
+ trm.Function = func;
+ trm.Type = CssTermType.Function;
+
+ while (m_lookaheadToken.m_tokenKind == 4)
+ {
+ Get();
+ }
+ Expect(11);
+ }
+ }
+ else if (StartOf(15))
+ {
+ if (m_lookaheadToken.m_tokenKind == 29)
+ {
+ Get();
+ trm.Sign = '+';
+ }
+ if (minus) { trm.Sign = '-'; }
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ if (m_lookaheadToken.m_tokenKind == 34)
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+ if (StartOf(20))
+ {
+ if (m_lookaheadToken.m_tokenValue.ToLower().Equals("n"))
+ {
+ Expect(22);
+ val += m_lastRecognizedToken.m_tokenValue;
+ if (m_lookaheadToken.m_tokenKind == 24 || m_lookaheadToken.m_tokenKind == 29)
+ {
+ if (m_lookaheadToken.m_tokenKind == 29)
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ else
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ Expect(3);
+ val += m_lastRecognizedToken.m_tokenValue;
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+ }
+ else if (m_lookaheadToken.m_tokenKind == 48)
+ {
+ Get();
+ trm.Unit = CssUnit.Percent;
+ }
+ else
+ {
+ if (IsUnitOfLength())
+ {
+ Identity(out ident);
+ try
+ {
+ trm.Unit = (CssUnit)Enum.Parse(typeof(CssUnit), ident, true);
+ }
+ catch
+ {
+ m_errors.SemanticError(m_lastRecognizedToken.m_tokenLine, m_lastRecognizedToken.m_tokenColumn, string.Format("Unrecognized unit '{0}'", ident));
+ }
+
+ }
+ }
+ }
+ trm.Value = val; trm.Type = CssTermType.Number;
+ }
+ else SyntaxErr(59);
+ }
+ else SyntaxErr(60);
+ }
+
+ void HexValue(out string val)
+ {
+ val = "";
+ bool found = false;
+
+ Expect(33);
+ val += m_lastRecognizedToken.m_tokenValue;
+ if (StartOf(19))
+ {
+ while (m_lookaheadToken.m_tokenKind == 3)
+ {
+ Get();
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+ else if (IsInHex(val))
+ {
+ Expect(1);
+ val += m_lastRecognizedToken.m_tokenValue; found = true;
+ }
+ else SyntaxErr(61);
+ if (!found && IsInHex(val))
+ {
+ Expect(1);
+ val += m_lastRecognizedToken.m_tokenValue;
+ }
+ }
+
+ public void Parse()
+ {
+ m_lookaheadToken = new CssToken();
+ m_lookaheadToken.m_tokenValue = "";
+ Get();
+ Css3();
+ Expect(0);
+ }
+
+ static readonly bool[,] set = {
+ {T,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,T, T,x,x,x, x,x,x,x, T,T,T,T, x,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, T,x,x,x, x,x,x,x, T,T,T,T, x,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, T,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x},
+ {x,T,x,T, T,x,x,T, T,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, T,T,T,T, x,T,x,x, x,T,T,x, x,x,x,x, x,x,x,x, x,x,T,T, T,x,x},
+ {x,x,x,x, x,x,x,x, x,x,x,x, T,T,T,T, T,T,T,T, T,T,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,T, T,x,x,x, T,x,x,x, T,T,T,T, x,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,T,T,T, T,T,T,x, 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,x},
+ {x,T,T,T, T,T,T,T, x,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,x},
+ {x,T,T,T, T,T,T,T, T,T,x,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,x},
+ {x,T,T,T, x,T,T,x, x,T,x,x, 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,x},
+ {x,T,x,T, T,x,x,T, T,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, T,T,x,x, x,T,x,x, x,T,T,x, x,x,x,x, x,x,x,x, x,x,T,T, T,x,x},
+ {x,T,x,x, T,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, T,x,x,x, x,T,T,T, T,T,T,T, x,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,T,T,T, x,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, T,T,T,T, T,T,x,x, x,x,x,x, x,x,x},
+ {x,T,x,T, T,x,x,T, T,T,x,T, T,T,T,T, T,T,T,T, T,T,T,T, T,T,T,T, T,T,x,x, T,T,T,T, x,x,x,x, x,x,x,T, T,x,T,T, T,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x},
+ {x,x,x,x, x,x,x,x, x,x,T,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,T,x, T,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, T,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,T, x,x,x,x, x,x,x},
+ {x,T,x,T, T,x,x,T, T,T,T,T, T,T,T,T, T,T,T,T, T,T,T,T, T,T,T,T, T,T,x,x, T,T,T,T, T,x,x,x, x,x,x,T, T,x,T,T, T,x,x},
+ {x,T,x,x, x,x,x,x, x,T,x,x, T,T,T,T, T,T,T,T, T,T,T,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, x,x,x,x, T,x,x}
+ };
+ }
+
+
+ public class Errors
+ {
+ public int numberOfErrorsDetected = 0;
+ public string errMsgFormat = "CssParser error: line {0} col {1}: {2}";
+
+ public virtual void SyntaxError(int line, int col, int n)
+ {
+ string s;
+ switch (n)
+ {
+ case 0: s = "EOF expected";
+ break;
+ case 1: s = "identifier expected";
+ break;
+ case 2: s = "newline expected";
+ break;
+ case 3: s = "digit expected";
+ break;
+ case 4: s = "whitespace expected";
+ break;
+ case 5: s = "\"<!--\" expected";
+ break;
+ case 6: s = "\"-->\" expected";
+ break;
+ case 7: s = "\"\'\" expected";
+ break;
+ case 8: s = "\"\"\" expected";
+ break;
+ case 9: s = "\"url\" expected";
+ break;
+ case 10: s = "\"(\" expected";
+ break;
+ case 11: s = "\")\" expected";
+ break;
+ case 12: s = "\"all\" expected";
+ break;
+ case 13: s = "\"aural\" expected";
+ break;
+ case 14: s = "\"braille\" expected";
+ break;
+ case 15: s = "\"embossed\" expected";
+ break;
+ case 16: s = "\"handheld\" expected";
+ break;
+ case 17: s = "\"print\" expected";
+ break;
+ case 18: s = "\"projection\" expected";
+ break;
+ case 19: s = "\"screen\" expected";
+ break;
+ case 20: s = "\"tty\" expected";
+ break;
+ case 21: s = "\"tv\" expected";
+ break;
+ case 22: s = "\"n\" expected";
+ break;
+ case 23: s = "\"@\" expected";
+ break;
+ case 24: s = "\"-\" expected";
+ break;
+ case 25: s = "\",\" expected";
+ break;
+ case 26: s = "\"{\" expected";
+ break;
+ case 27: s = "\";\" expected";
+ break;
+ case 28: s = "\"}\" expected";
+ break;
+ case 29: s = "\"+\" expected";
+ break;
+ case 30: s = "\">\" expected";
+ break;
+ case 31: s = "\"~\" expected";
+ break;
+ case 32: s = "\"*\" expected";
+ break;
+ case 33: s = "\"#\" expected";
+ break;
+ case 34: s = "\".\" expected";
+ break;
+ case 35: s = "\"[\" expected";
+ break;
+ case 36: s = "\"=\" expected";
+ break;
+ case 37: s = "\"~=\" expected";
+ break;
+ case 38: s = "\"|=\" expected";
+ break;
+ case 39: s = "\"$=\" expected";
+ break;
+ case 40: s = "\"^=\" expected";
+ break;
+ case 41: s = "\"*=\" expected";
+ break;
+ case 42: s = "\"]\" expected";
+ break;
+ case 43: s = "\":\" expected";
+ break;
+ case 44: s = "\"!\" expected";
+ break;
+ case 45: s = "\"important\" expected";
+ break;
+ case 46: s = "\"/\" expected";
+ break;
+ case 47: s = "\"U\\\\\" expected";
+ break;
+ case 48: s = "\"%\" expected";
+ break;
+ case 49: s = "??? expected";
+ break;
+ case 50: s = "invalid directive";
+ break;
+ case 51: s = "invalid QuotedString";
+ break;
+ case 52: s = "invalid URI";
+ break;
+ case 53: s = "invalid medium";
+ break;
+ case 54: s = "invalid identity";
+ break;
+ case 55: s = "invalid simpleselector";
+ break;
+ case 56: s = "invalid attrib";
+ break;
+ case 57: s = "invalid term";
+ break;
+ case 58: s = "invalid term";
+ break;
+ case 59: s = "invalid term";
+ break;
+ case 60: s = "invalid term";
+ break;
+ case 61: s = "invalid HexValue";
+ break;
+
+ default: s = "error " + n;
+ break;
+ }
+ var errorString = string.Format(errMsgFormat, line, col, s);
+ throw new OpenXmlPowerToolsException(errorString);
+ }
+
+ public virtual void SemanticError(int line, int col, string s)
+ {
+ var errorString = string.Format(errMsgFormat, line, col, s);
+ throw new OpenXmlPowerToolsException(errorString);
+ }
+
+ public virtual void SemanticError(string s)
+ {
+ throw new OpenXmlPowerToolsException(s);
+ }
+
+ public virtual void Warning(int line, int col, string s)
+ {
+ var errorString = string.Format(errMsgFormat, line, col, s);
+ throw new OpenXmlPowerToolsException(errorString);
+ }
+
+ public virtual void Warning(string s)
+ {
+ throw new OpenXmlPowerToolsException(s);
+ }
+ }
+
+
+ public class FatalError : Exception
+ {
+ public FatalError(string m) : base(m) { }
+ }
+
+ public class CssToken
+ {
+ public int m_tokenKind;
+ public int m_tokenPositionInBytes;
+ public int m_tokenPositionInCharacters;
+ public int m_tokenColumn;
+ public int m_tokenLine;
+ public string m_tokenValue;
+ public CssToken m_nextToken;
+ }
+
+ public class CssBuffer
+ {
+ public const int EOF = char.MaxValue + 1;
+ const int MIN_BUFFER_LENGTH = 1024;
+ const int MAX_BUFFER_LENGTH = MIN_BUFFER_LENGTH * 64;
+ byte[] m_inputBuffer;
+ int m_bufferStart;
+ int m_bufferLength;
+ int m_inputStreamLength;
+ int m_currentPositionInBuffer;
+ Stream m_inputStream;
+ bool m_isUserStream;
+
+ public CssBuffer(Stream s, bool isUserStream)
+ {
+ m_inputStream = s; this.m_isUserStream = isUserStream;
+
+ if (m_inputStream.CanSeek)
+ {
+ m_inputStreamLength = (int)m_inputStream.Length;
+ m_bufferLength = Math.Min(m_inputStreamLength, MAX_BUFFER_LENGTH);
+ m_bufferStart = Int32.MaxValue;
+ }
+ else
+ {
+ m_inputStreamLength = m_bufferLength = m_bufferStart = 0;
+ }
+
+ m_inputBuffer = new byte[(m_bufferLength > 0) ? m_bufferLength : MIN_BUFFER_LENGTH];
+ if (m_inputStreamLength > 0)
+ Pos = 0;
+ else
+ m_currentPositionInBuffer = 0;
+ if (m_bufferLength == m_inputStreamLength && m_inputStream.CanSeek)
+ Close();
+ }
+
+ protected CssBuffer(CssBuffer b)
+ {
+ m_inputBuffer = b.m_inputBuffer;
+ m_bufferStart = b.m_bufferStart;
+ m_bufferLength = b.m_bufferLength;
+ m_inputStreamLength = b.m_inputStreamLength;
+ m_currentPositionInBuffer = b.m_currentPositionInBuffer;
+ m_inputStream = b.m_inputStream;
+ b.m_inputStream = null;
+ m_isUserStream = b.m_isUserStream;
+ }
+
+ ~CssBuffer() { Close(); }
+
+ protected void Close()
+ {
+ if (!m_isUserStream && m_inputStream != null)
+ {
+ m_inputStream.Close();
+ m_inputStream = null;
+ }
+ }
+
+ public virtual int Read()
+ {
+ if (m_currentPositionInBuffer < m_bufferLength)
+ {
+ return m_inputBuffer[m_currentPositionInBuffer++];
+ }
+ else if (Pos < m_inputStreamLength)
+ {
+ Pos = Pos;
+ return m_inputBuffer[m_currentPositionInBuffer++];
+ }
+ else if (m_inputStream != null && !m_inputStream.CanSeek && ReadNextStreamChunk() > 0)
+ {
+ return m_inputBuffer[m_currentPositionInBuffer++];
+ }
+ else
+ {
+ return EOF;
+ }
+ }
+
+ public int Peek()
+ {
+ int curPos = Pos;
+ int ch = Read();
+ Pos = curPos;
+ return ch;
+ }
+
+ public string GetString(int beg, int end)
+ {
+ int len = 0;
+ char[] buf = new char[end - beg];
+ int oldPos = Pos;
+ Pos = beg;
+ while (Pos < end)
+ buf[len++] = (char)Read();
+ Pos = oldPos;
+ return new String(buf, 0, len);
+ }
+
+ public int Pos
+ {
+ get { return m_currentPositionInBuffer + m_bufferStart; }
+ set
+ {
+ if (value >= m_inputStreamLength && m_inputStream != null && !m_inputStream.CanSeek)
+ {
+ while (value >= m_inputStreamLength && ReadNextStreamChunk() > 0) ;
+ }
+
+ if (value < 0 || value > m_inputStreamLength)
+ {
+ throw new FatalError("buffer out of bounds access, position: " + value);
+ }
+
+ if (value >= m_bufferStart && value < m_bufferStart + m_bufferLength)
+ {
+ m_currentPositionInBuffer = value - m_bufferStart;
+ }
+ else if (m_inputStream != null)
+ {
+ m_inputStream.Seek(value, SeekOrigin.Begin);
+ m_bufferLength = m_inputStream.Read(m_inputBuffer, 0, m_inputBuffer.Length);
+ m_bufferStart = value; m_currentPositionInBuffer = 0;
+ }
+ else
+ {
+ m_currentPositionInBuffer = m_inputStreamLength - m_bufferStart;
+ }
+ }
+ }
+
+ private int ReadNextStreamChunk()
+ {
+ int free = m_inputBuffer.Length - m_bufferLength;
+ if (free == 0)
+ {
+ byte[] newBuf = new byte[m_bufferLength * 2];
+ Array.Copy(m_inputBuffer, newBuf, m_bufferLength);
+ m_inputBuffer = newBuf;
+ free = m_bufferLength;
+ }
+ int read = m_inputStream.Read(m_inputBuffer, m_bufferLength, free);
+ if (read > 0)
+ {
+ m_inputStreamLength = m_bufferLength = (m_bufferLength + read);
+ return read;
+ }
+ return 0;
+ }
+ }
+
+ public class UTF8Buffer : CssBuffer
+ {
+ public UTF8Buffer(CssBuffer b) : base(b) { }
+
+ public override int Read()
+ {
+ int ch;
+ do
+ {
+ ch = base.Read();
+ } while ((ch >= 128) && ((ch & 0xC0) != 0xC0) && (ch != EOF));
+ if (ch < 128 || ch == EOF)
+ {
+ // nothing to do
+ }
+ else if ((ch & 0xF0) == 0xF0)
+ {
+ int c1 = ch & 0x07;
+ ch = base.Read();
+ int c2 = ch & 0x3F;
+ ch = base.Read();
+ int c3 = ch & 0x3F;
+ ch = base.Read();
+ int c4 = ch & 0x3F;
+ ch = (((((c1 << 6) | c2) << 6) | c3) << 6) | c4;
+ }
+ else if ((ch & 0xE0) == 0xE0)
+ {
+ int c1 = ch & 0x0F;
+ ch = base.Read();
+ int c2 = ch & 0x3F;
+ ch = base.Read();
+ int c3 = ch & 0x3F;
+ ch = (((c1 << 6) | c2) << 6) | c3;
+ }
+ else if ((ch & 0xC0) == 0xC0)
+ {
+ int c1 = ch & 0x1F;
+ ch = base.Read();
+ int c2 = ch & 0x3F;
+ ch = (c1 << 6) | c2;
+ }
+ return ch;
+ }
+ }
+
+ public class Scanner
+ {
+ const char END_OF_LINE = '\n';
+ const int c_eof = 0;
+ const int c_maxT = 49;
+ const int c_noSym = 49;
+ const int c_maxTokenLength = 128;
+
+ public CssBuffer m_scannerBuffer;
+
+ CssToken m_currentToken;
+ int m_currentInputCharacter;
+ int m_currentCharacterBytePosition;
+ int m_unicodeCharacterPosition;
+ int m_columnNumberOfCurrentCharacter;
+ int m_lineNumberOfCurrentCharacter;
+ int m_eolInComment;
+ static readonly Hashtable s_start;
+
+ CssToken m_tokensAlreadyPeeked;
+ CssToken m_currentPeekToken;
+
+ char[] m_textOfCurrentToken = new char[c_maxTokenLength];
+ int m_lengthOfCurrentToken;
+
+ static Scanner()
+ {
+ s_start = new Hashtable(128);
+ for (int i = 65; i <= 84; ++i)
+ s_start[i] = 1;
+ for (int i = 86; i <= 90; ++i)
+ s_start[i] = 1;
+ for (int i = 95; i <= 95; ++i)
+ s_start[i] = 1;
+ for (int i = 97; i <= 122; ++i)
+ s_start[i] = 1;
+ for (int i = 10; i <= 10; ++i)
+ s_start[i] = 2;
+ for (int i = 13; i <= 13; ++i)
+ s_start[i] = 2;
+ for (int i = 48; i <= 57; ++i)
+ s_start[i] = 3;
+ for (int i = 9; i <= 9; ++i)
+ s_start[i] = 4;
+ for (int i = 11; i <= 12; ++i)
+ s_start[i] = 4;
+ for (int i = 32; i <= 32; ++i)
+ s_start[i] = 4;
+ s_start[60] = 5;
+ s_start[45] = 40;
+ s_start[39] = 11;
+ s_start[34] = 12;
+ s_start[40] = 13;
+ s_start[41] = 14;
+ s_start[64] = 15;
+ s_start[44] = 16;
+ s_start[123] = 17;
+ s_start[59] = 18;
+ s_start[125] = 19;
+ s_start[43] = 20;
+ s_start[62] = 21;
+ s_start[126] = 41;
+ s_start[42] = 42;
+ s_start[35] = 22;
+ s_start[46] = 23;
+ s_start[91] = 24;
+ s_start[61] = 25;
+ s_start[124] = 27;
+ s_start[36] = 29;
+ s_start[94] = 31;
+ s_start[93] = 34;
+ s_start[58] = 35;
+ s_start[33] = 36;
+ s_start[47] = 37;
+ s_start[85] = 43;
+ s_start[37] = 39;
+ s_start[CssBuffer.EOF] = -1;
+
+ }
+
+ public Scanner(string fileName)
+ {
+ try
+ {
+ Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
+ m_scannerBuffer = new CssBuffer(stream, false);
+ Init();
+ }
+ catch (IOException)
+ {
+ throw new FatalError("Cannot open file " + fileName);
+ }
+ }
+
+ public Scanner(Stream s)
+ {
+ m_scannerBuffer = new CssBuffer(s, true);
+ Init();
+ }
+
+ void Init()
+ {
+ m_currentCharacterBytePosition = -1;
+ m_lineNumberOfCurrentCharacter = 1;
+ m_columnNumberOfCurrentCharacter = 0;
+ m_unicodeCharacterPosition = -1;
+ m_eolInComment = 0;
+ NextCh();
+ if (m_currentInputCharacter == 0xEF)
+ {
+ NextCh();
+ int ch1 = m_currentInputCharacter;
+ NextCh();
+ int ch2 = m_currentInputCharacter;
+ if (ch1 != 0xBB || ch2 != 0xBF)
+ {
+ throw new FatalError(String.Format("illegal byte order mark: EF {0,2:X} {1,2:X}", ch1, ch2));
+ }
+ m_scannerBuffer = new UTF8Buffer(m_scannerBuffer);
+ m_columnNumberOfCurrentCharacter = 0;
+ m_unicodeCharacterPosition = -1;
+ NextCh();
+ }
+ m_currentPeekToken = m_tokensAlreadyPeeked = new CssToken();
+ }
+
+ void NextCh()
+ {
+ if (m_eolInComment > 0)
+ {
+ m_currentInputCharacter = END_OF_LINE;
+ m_eolInComment--;
+ }
+ else
+ {
+ m_currentCharacterBytePosition = m_scannerBuffer.Pos;
+ m_currentInputCharacter = m_scannerBuffer.Read();
+ m_columnNumberOfCurrentCharacter++;
+ m_unicodeCharacterPosition++;
+ if (m_currentInputCharacter == '\r' && m_scannerBuffer.Peek() != '\n')
+ m_currentInputCharacter = END_OF_LINE;
+ if (m_currentInputCharacter == END_OF_LINE)
+ {
+ m_lineNumberOfCurrentCharacter++; m_columnNumberOfCurrentCharacter = 0;
+ }
+ }
+
+ }
+
+ void AddCh()
+ {
+ if (m_lengthOfCurrentToken >= m_textOfCurrentToken.Length)
+ {
+ char[] newBuf = new char[2 * m_textOfCurrentToken.Length];
+ Array.Copy(m_textOfCurrentToken, 0, newBuf, 0, m_textOfCurrentToken.Length);
+ m_textOfCurrentToken = newBuf;
+ }
+ if (m_currentInputCharacter != CssBuffer.EOF)
+ {
+ m_textOfCurrentToken[m_lengthOfCurrentToken++] = (char)m_currentInputCharacter;
+ NextCh();
+ }
+ }
+
+ bool Comment0()
+ {
+ int level = 1, pos0 = m_currentCharacterBytePosition, line0 = m_lineNumberOfCurrentCharacter, col0 = m_columnNumberOfCurrentCharacter, charPos0 = m_unicodeCharacterPosition;
+ NextCh();
+ if (m_currentInputCharacter == '*')
+ {
+ NextCh();
+ for (;;)
+ {
+ if (m_currentInputCharacter == '*')
+ {
+ NextCh();
+ if (m_currentInputCharacter == '/')
+ {
+ level--;
+ if (level == 0)
+ {
+ m_eolInComment = m_lineNumberOfCurrentCharacter - line0;
+ NextCh();
+ return true;
+ }
+ NextCh();
+ }
+ }
+ else if (m_currentInputCharacter == CssBuffer.EOF)
+ return false;
+ else
+ NextCh();
+ }
+ }
+ else
+ {
+ m_scannerBuffer.Pos = pos0;
+ NextCh();
+ m_lineNumberOfCurrentCharacter = line0;
+ m_columnNumberOfCurrentCharacter = col0;
+ m_unicodeCharacterPosition = charPos0;
+ }
+ return false;
+ }
+
+
+ void CheckLiteral()
+ {
+ switch (m_currentToken.m_tokenValue)
+ {
+ case "url":
+ m_currentToken.m_tokenKind = 9;
+ break;
+ case "all":
+ m_currentToken.m_tokenKind = 12;
+ break;
+ case "aural":
+ m_currentToken.m_tokenKind = 13;
+ break;
+ case "braille":
+ m_currentToken.m_tokenKind = 14;
+ break;
+ case "embossed":
+ m_currentToken.m_tokenKind = 15;
+ break;
+ case "handheld":
+ m_currentToken.m_tokenKind = 16;
+ break;
+ case "print":
+ m_currentToken.m_tokenKind = 17;
+ break;
+ case "projection":
+ m_currentToken.m_tokenKind = 18;
+ break;
+ case "screen":
+ m_currentToken.m_tokenKind = 19;
+ break;
+ case "tty":
+ m_currentToken.m_tokenKind = 20;
+ break;
+ case "tv":
+ m_currentToken.m_tokenKind = 21;
+ break;
+ case "n":
+ m_currentToken.m_tokenKind = 22;
+ break;
+ case "important":
+ m_currentToken.m_tokenKind = 45;
+ break;
+ default:
+ break;
+ }
+ }
+
+ CssToken NextToken()
+ {
+ while (m_currentInputCharacter == ' ' || m_currentInputCharacter == 10 || m_currentInputCharacter == 13)
+ NextCh();
+ if (m_currentInputCharacter == '/' && Comment0())
+ return NextToken();
+ int recKind = c_noSym;
+ int recEnd = m_currentCharacterBytePosition;
+ m_currentToken = new CssToken();
+ m_currentToken.m_tokenPositionInBytes = m_currentCharacterBytePosition;
+ m_currentToken.m_tokenColumn = m_columnNumberOfCurrentCharacter;
+ m_currentToken.m_tokenLine = m_lineNumberOfCurrentCharacter;
+ m_currentToken.m_tokenPositionInCharacters = m_unicodeCharacterPosition;
+ int state;
+ if (s_start.ContainsKey(m_currentInputCharacter))
+ {
+ state = (int)s_start[m_currentInputCharacter];
+ }
+ else {
+ state = 0;
+ }
+ m_lengthOfCurrentToken = 0;
+ AddCh();
+
+ switch (state)
+ {
+ case -1: {
+ m_currentToken.m_tokenKind = c_eof;
+ break;
+ }
+ case 0:
+ {
+ if (recKind != c_noSym)
+ {
+ m_lengthOfCurrentToken = recEnd - m_currentToken.m_tokenPositionInBytes;
+ SetScannerBehindT();
+ }
+ m_currentToken.m_tokenKind = recKind;
+ break;
+ }
+ case 1:
+ recEnd = m_currentCharacterBytePosition; recKind = 1;
+ if (m_currentInputCharacter == '-' || m_currentInputCharacter >= '0' && m_currentInputCharacter <= '9' || m_currentInputCharacter >= 'A' && m_currentInputCharacter <= 'Z' || m_currentInputCharacter == '_' || m_currentInputCharacter >= 'a' && m_currentInputCharacter <= 'z')
+ {
+ AddCh();
+ goto case 1;
+ }
+ else
+ {
+ m_currentToken.m_tokenKind = 1; m_currentToken.m_tokenValue = new String(m_textOfCurrentToken, 0, m_lengthOfCurrentToken);
+ CheckLiteral();
+ return m_currentToken;
+ }
+ case 2:
+ {
+ m_currentToken.m_tokenKind = 2;
+ break;
+ }
+ case 3:
+ {
+ m_currentToken.m_tokenKind = 3;
+ break;
+ }
+ case 4:
+ {
+ m_currentToken.m_tokenKind = 4;
+ break;
+ }
+ case 5:
+ if (m_currentInputCharacter == '!')
+ {
+ AddCh();
+ goto case 6;
+ }
+ else
+ {
+ goto case 0;
+ }
+ case 6:
+ if (m_currentInputCharacter == '-')
+ {
+ AddCh();
+ goto case 7;
+ }
+ else {
+ goto case 0;
+ }
+ case 7:
+ if (m_currentInputCharacter == '-')
+ {
+ AddCh();
+ goto case 8;
+ }
+ else
+ {
+ goto case 0;
+ }
+ case 8:
+ {
+ m_currentToken.m_tokenKind = 5;
+ break;
+ }
+ case 9:
+ if (m_currentInputCharacter == '>')
+ {
+ AddCh();
+ goto case 10;
+ }
+ else
+ {
+ goto case 0;
+ }
+ case 10:
+ {
+ m_currentToken.m_tokenKind = 6;
+ break;
+ }
+ case 11:
+ {
+ m_currentToken.m_tokenKind = 7;
+ break;
+ }
+ case 12:
+ {
+ m_currentToken.m_tokenKind = 8;
+ break;
+ }
+ case 13:
+ {
+ m_currentToken.m_tokenKind = 10;
+ break;
+ }
+ case 14:
+ {
+ m_currentToken.m_tokenKind = 11;
+ break;
+ }
+ case 15:
+ {
+ m_currentToken.m_tokenKind = 23;
+ break;
+ }
+ case 16:
+ {
+ m_currentToken.m_tokenKind = 25;
+ break;
+ }
+ case 17:
+ {
+ m_currentToken.m_tokenKind = 26;
+ break;
+ }
+ case 18:
+ {
+ m_currentToken.m_tokenKind = 27;
+ break;
+ }
+ case 19:
+ {
+ m_currentToken.m_tokenKind = 28;
+ break;
+ }
+ case 20:
+ {
+ m_currentToken.m_tokenKind = 29;
+ break;
+ }
+ case 21:
+ {
+ m_currentToken.m_tokenKind = 30;
+ break;
+ }
+ case 22:
+ {
+ m_currentToken.m_tokenKind = 33;
+ break;
+ }
+ case 23:
+ {
+ m_currentToken.m_tokenKind = 34;
+ break;
+ }
+ case 24:
+ {
+ m_currentToken.m_tokenKind = 35;
+ break;
+ }
+ case 25:
+ {
+ m_currentToken.m_tokenKind = 36;
+ break;
+ }
+ case 26:
+ {
+ m_currentToken.m_tokenKind = 37;
+ break;
+ }
+ case 27:
+ if (m_currentInputCharacter == '=')
+ {
+ AddCh();
+ goto case 28;
+ }
+ else {
+ goto case 0;
+ }
+ case 28:
+ {
+ m_currentToken.m_tokenKind = 38;
+ break;
+ }
+ case 29:
+ if (m_currentInputCharacter == '=')
+ {
+ AddCh();
+ goto case 30;
+ }
+ else {
+ goto case 0;
+ }
+ case 30:
+ {
+ m_currentToken.m_tokenKind = 39;
+ break;
+ }
+ case 31:
+ if (m_currentInputCharacter == '=')
+ {
+ AddCh();
+ goto case 32;
+ }
+ else
+ {
+ goto case 0;
+ }
+ case 32:
+ {
+ m_currentToken.m_tokenKind = 40;
+ break;
+ }
+ case 33:
+ {
+ m_currentToken.m_tokenKind = 41;
+ break;
+ }
+ case 34:
+ {
+ m_currentToken.m_tokenKind = 42;
+ break;
+ }
+ case 35:
+ {
+ m_currentToken.m_tokenKind = 43;
+ break;
+ }
+ case 36:
+ {
+ m_currentToken.m_tokenKind = 44;
+ break;
+ }
+ case 37:
+ {
+ m_currentToken.m_tokenKind = 46;
+ break;
+ }
+ case 38:
+ {
+ m_currentToken.m_tokenKind = 47;
+ break;
+ }
+ case 39:
+ {
+ m_currentToken.m_tokenKind = 48;
+ break;
+ }
+ case 40:
+ recEnd = m_currentCharacterBytePosition;
+ recKind = 24;
+ if (m_currentInputCharacter == '-')
+ {
+ AddCh();
+ goto case 9;
+ }
+ else
+ {
+ m_currentToken.m_tokenKind = 24;
+ break;
+ }
+ case 41:
+ recEnd = m_currentCharacterBytePosition;
+ recKind = 31;
+ if (m_currentInputCharacter == '=')
+ {
+ AddCh();
+ goto case 26;
+ }
+ else
+ {
+ m_currentToken.m_tokenKind = 31;
+ break;
+ }
+ case 42:
+ recEnd = m_currentCharacterBytePosition;
+ recKind = 32;
+ if (m_currentInputCharacter == '=')
+ {
+ AddCh();
+ goto case 33;
+ }
+ else
+ {
+ m_currentToken.m_tokenKind = 32;
+ break;
+ }
+ case 43:
+ recEnd = m_currentCharacterBytePosition; recKind = 1;
+ if (m_currentInputCharacter == '-' || m_currentInputCharacter >= '0' && m_currentInputCharacter <= '9' || m_currentInputCharacter >= 'A' && m_currentInputCharacter <= 'Z' || m_currentInputCharacter == '_' || m_currentInputCharacter >= 'a' && m_currentInputCharacter <= 'z')
+ {
+ AddCh();
+ goto case 1;
+ }
+ else if (m_currentInputCharacter == 92)
+ {
+ AddCh();
+ goto case 38;
+ }
+ else
+ {
+ m_currentToken.m_tokenKind = 1;
+ m_currentToken.m_tokenValue = new String(m_textOfCurrentToken, 0, m_lengthOfCurrentToken);
+ CheckLiteral();
+ return m_currentToken;
+ }
+
+ }
+ m_currentToken.m_tokenValue = new String(m_textOfCurrentToken, 0, m_lengthOfCurrentToken);
+ return m_currentToken;
+ }
+
+ private void SetScannerBehindT()
+ {
+ m_scannerBuffer.Pos = m_currentToken.m_tokenPositionInBytes;
+ NextCh();
+ m_lineNumberOfCurrentCharacter = m_currentToken.m_tokenLine; m_columnNumberOfCurrentCharacter = m_currentToken.m_tokenColumn; m_unicodeCharacterPosition = m_currentToken.m_tokenPositionInCharacters;
+ for (int i = 0; i < m_lengthOfCurrentToken; i++) NextCh();
+ }
+
+ public CssToken Scan()
+ {
+ if (m_tokensAlreadyPeeked.m_nextToken == null)
+ {
+ return NextToken();
+ }
+ else
+ {
+ m_currentPeekToken = m_tokensAlreadyPeeked = m_tokensAlreadyPeeked.m_nextToken;
+ return m_tokensAlreadyPeeked;
+ }
+ }
+
+ public CssToken Peek()
+ {
+ do
+ {
+ if (m_currentPeekToken.m_nextToken == null)
+ {
+ m_currentPeekToken.m_nextToken = NextToken();
+ }
+ m_currentPeekToken = m_currentPeekToken.m_nextToken;
+ } while (m_currentPeekToken.m_tokenKind > c_maxT);
+
+ return m_currentPeekToken;
+ }
+
+ public void ResetPeek()
+ {
+ m_currentPeekToken = m_tokensAlreadyPeeked;
+ }
+ }
+
+}
diff --git a/OpenXmlPowerTools/ListItemRetriever.cs b/OpenXmlPowerTools/ListItemRetriever.cs
new file mode 100644
index 0000000..a55b02e
--- /dev/null
+++ b/OpenXmlPowerTools/ListItemRetriever.cs
@@ -0,0 +1,1198 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 2.7.00
+ * Complete re-write - new architecture enables much more accurate rendering of list items.
+
+Version: 2.6.03
+ * Fixed: Empty paragraphs were not counted properly
+ * Fixed: Numbered styles were not processed properly if they derived from another style
+ * Now has a dependency on FormattingAssembler.cs
+
+Version: 2.6.00
+ * Enhancements to support HtmlConverter.cs
+
+Relevant Screen-Casts
+ * https://www.youtube.com/watch?v=w9h1VQ3eR_Q
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class ListItemRetrieverSettings
+ {
+ public static Dictionary<string, Func<string, int, string, string>> DefaultListItemTextImplementations =
+ new Dictionary<string, Func<string, int, string, string>>()
+ {
+ {"fr-FR", ListItemTextGetter_fr_FR.GetListItemText},
+ {"tr-TR", ListItemTextGetter_tr_TR.GetListItemText},
+ {"ru-RU", ListItemTextGetter_ru_RU.GetListItemText},
+ {"sv-SE", ListItemTextGetter_sv_SE.GetListItemText},
+ {"zh-CN", ListItemTextGetter_zh_CN.GetListItemText},
+ };
+ public Dictionary<string, Func<string, int, string, string>> ListItemTextImplementations;
+ public ListItemRetrieverSettings()
+ {
+ ListItemTextImplementations = DefaultListItemTextImplementations;
+ }
+ }
+
+ public class ListItemRetriever
+ {
+ public class ListItemSourceSet
+ {
+ public int NumId; // numId from the paragraph or style
+ public XElement Num; // num element from the numbering part
+ public int AbstractNumId; // abstract numId
+ public XElement AbstractNum; // abstractNum element
+
+ public ListItemSourceSet(XDocument numXDoc, XDocument styleXDoc, int numId)
+ {
+ NumId = numId;
+
+ Num = numXDoc
+ .Root
+ .Elements(W.num)
+ .FirstOrDefault(n => (int)n.Attribute(W.numId) == numId);
+
+ AbstractNumId = (int)Num
+ .Elements(W.abstractNumId)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ AbstractNum = numXDoc
+ .Root
+ .Elements(W.abstractNum)
+ .Where(e => (int)e.Attribute(W.abstractNumId) == AbstractNumId)
+ .FirstOrDefault();
+ }
+
+ public int? StartOverride(int ilvl)
+ {
+ var lvlOverride = Num
+ .Elements(W.lvlOverride)
+ .FirstOrDefault(nlo => (int)nlo.Attribute(W.ilvl) == ilvl);
+ if (lvlOverride != null)
+ return (int?)lvlOverride
+ .Elements(W.startOverride)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ return null;
+ }
+
+ public XElement OverrideLvl(int ilvl)
+ {
+ var lvlOverride = Num
+ .Elements(W.lvlOverride)
+ .FirstOrDefault(nlo => (int)nlo.Attribute(W.ilvl) == ilvl);
+ if (lvlOverride != null)
+ return lvlOverride.Element(W.lvl);
+ return null;
+ }
+
+ public XElement AbstractLvl(int ilvl)
+ {
+ return AbstractNum
+ .Elements(W.lvl)
+ .FirstOrDefault(al => (int)al.Attribute(W.ilvl) == ilvl);
+ }
+
+ public XElement Lvl(int ilvl)
+ {
+ var overrideLvl = OverrideLvl(ilvl);
+ if (overrideLvl != null)
+ return overrideLvl;
+ return AbstractLvl(ilvl);
+ }
+ }
+
+ public class ListItemSource
+ {
+ public ListItemSourceSet Main;
+ public string NumStyleLinkName;
+ public ListItemSourceSet NumStyleLink;
+ public int Style_ilvl;
+
+ // for list item sources that use numStyleLink, there are two abstractId values.
+ // The abstractId that is use is in num->abstractNum->numStyleLink->style->num->abstractNum
+
+ public ListItemSource(XDocument numXDoc, XDocument stylesXDoc, int numId)
+ {
+ Main = new ListItemSourceSet(numXDoc, stylesXDoc, numId);
+
+ NumStyleLinkName = (string)Main
+ .AbstractNum
+ .Elements(W.numStyleLink)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (NumStyleLinkName != null)
+ {
+ var numStyleLinkNumId = (int?)stylesXDoc
+ .Root
+ .Elements(W.style)
+ .Where(s => (string)s.Attribute(W.styleId) == NumStyleLinkName)
+ .Elements(W.pPr)
+ .Elements(W.numPr)
+ .Elements(W.numId)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (numStyleLinkNumId != null)
+ NumStyleLink = new ListItemSourceSet(numXDoc, stylesXDoc, (int)numStyleLinkNumId);
+ }
+ }
+
+ public XElement Lvl(int ilvl)
+ {
+ if (NumStyleLink != null)
+ {
+ var lvl = NumStyleLink.Lvl(ilvl);
+ if (lvl == null)
+ {
+ for (int i = ilvl - 1; i >= 0; i--)
+ {
+ lvl = NumStyleLink.Lvl(i);
+ if (lvl != null)
+ break;
+ }
+ }
+ return lvl;
+ }
+ var lvl2 = Main.Lvl(ilvl);
+ if (lvl2 == null)
+ {
+ for (int i = ilvl - 1; i >= 0; i--)
+ {
+ lvl2 = Main.Lvl(i);
+ if (lvl2 != null)
+ break;
+ }
+ }
+ return lvl2;
+ }
+
+ public int? StartOverride(int ilvl)
+ {
+ if (NumStyleLink != null)
+ {
+ var startOverride = NumStyleLink.StartOverride(ilvl);
+ if (startOverride != null)
+ return startOverride;
+ }
+ return Main.StartOverride(ilvl);
+ }
+
+ public int Start(int ilvl)
+ {
+ var lvl = Lvl(ilvl);
+ var start = (int?)lvl.Elements(W.start).Attributes(W.val).FirstOrDefault();
+ if (start != null)
+ return (int)start;
+ return 0;
+ }
+
+ public int AbstractNumId
+ {
+ get
+ {
+ return Main.AbstractNumId;
+ }
+ }
+ }
+
+ public class ListItemInfo
+ {
+ public bool IsListItem;
+ public bool IsZeroNumId;
+
+ public ListItemSource FromStyle;
+ public ListItemSource FromParagraph;
+
+ private int? mAbstractNumId = null;
+
+ public int? AbstractNumId
+ {
+ get
+ {
+ // note: this property does not get NumStyleLinkAbstractNumId
+ // it presumes that we are only interested in AbstractNumId
+ // however, it is easy enough to change if necessary
+
+ if (mAbstractNumId != null)
+ return mAbstractNumId;
+ if (FromParagraph != null)
+ mAbstractNumId = FromParagraph.AbstractNumId;
+ else if (FromStyle != null)
+ mAbstractNumId = FromStyle.AbstractNumId;
+ return mAbstractNumId;
+ }
+ }
+
+ public XElement Lvl(int ilvl)
+ {
+ if (FromParagraph != null)
+ {
+ var lvl = FromParagraph.Lvl(ilvl);
+ if (lvl == null)
+ {
+ for (int i = ilvl - 1; i >= 0; i--)
+ {
+ lvl = FromParagraph.Lvl(i);
+ if (lvl != null)
+ break;
+ }
+ }
+ return lvl;
+ }
+ var lvl2 = FromStyle.Lvl(ilvl);
+ if (lvl2 == null)
+ {
+ for (int i = ilvl - 1; i >= 0; i--)
+ {
+ lvl2 = FromParagraph.Lvl(i);
+ if (lvl2 != null)
+ break;
+ }
+ }
+ return lvl2;
+ }
+
+ public int Start(int ilvl)
+ {
+ if (FromParagraph != null)
+ return FromParagraph.Start(ilvl);
+ return FromStyle.Start(ilvl);
+ }
+
+ public int Start(int ilvl, bool takeOverride, out bool isOverride)
+ {
+ if (FromParagraph != null)
+ {
+ if (takeOverride)
+ {
+ var startOverride = FromParagraph.StartOverride(ilvl);
+ if (startOverride != null)
+ {
+ isOverride = true;
+ return (int)startOverride;
+ }
+ }
+ isOverride = false;
+ return FromParagraph.Start(ilvl);
+ }
+ else if (this.FromStyle != null)
+ {
+ if (takeOverride)
+ {
+ var startOverride = FromStyle.StartOverride(ilvl);
+ if (startOverride != null)
+ {
+ isOverride = true;
+ return (int)startOverride;
+ }
+ }
+ isOverride = false;
+ return FromStyle.Start(ilvl);
+ }
+ isOverride = false;
+ return 0;
+ }
+
+ public int? StartOverride(int ilvl)
+ {
+ if (FromParagraph != null)
+ {
+ var startOverride = FromParagraph.StartOverride(ilvl);
+ if (startOverride != null)
+ return (int)startOverride;
+ return null;
+ }
+ else if (this.FromStyle != null)
+ {
+ var startOverride = FromStyle.StartOverride(ilvl);
+ if (startOverride != null)
+ return (int)startOverride;
+ return null;
+ }
+ return null;
+ }
+
+ private int? mNumId;
+
+ public int NumId
+ {
+ get
+ {
+ if (mNumId != null)
+ return (int)mNumId;
+ if (FromParagraph != null)
+ mNumId = FromParagraph.Main.NumId;
+ else if (FromStyle != null)
+ mNumId = FromStyle.Main.NumId;
+ return (int)mNumId;
+ }
+ }
+
+ public ListItemInfo() { }
+
+ public ListItemInfo(bool isListItem, bool isZeroNumId)
+ {
+ IsListItem = isListItem;
+ IsZeroNumId = isZeroNumId;
+ }
+ }
+
+ public static void SetParagraphLevel(XElement paragraph, int ilvl)
+ {
+ var pi = paragraph.Annotation<ParagraphInfo>();
+ if (pi == null)
+ {
+ pi = new ParagraphInfo()
+ {
+ Ilvl = ilvl,
+ };
+ paragraph.AddAnnotation(pi);
+ return;
+ }
+ throw new OpenXmlPowerToolsException("Internal error - should never set ilvl more than once.");
+ }
+
+ public static int GetParagraphLevel(XElement paragraph)
+ {
+ var pi = paragraph.Annotation<ParagraphInfo>();
+ if (pi != null)
+ return pi.Ilvl;
+ throw new OpenXmlPowerToolsException("Internal error - should never ask for ilvl without it first being set.");
+ }
+
+ public static ListItemInfo GetListItemInfo(XDocument numXDoc, XDocument stylesXDoc, XElement paragraph)
+ {
+ // The following is an optimization - only determine ListItemInfo once for a
+ // paragraph.
+ ListItemInfo listItemInfo = paragraph.Annotation<ListItemInfo>();
+ if (listItemInfo != null)
+ return listItemInfo;
+ throw new OpenXmlPowerToolsException("Attempting to retrieve ListItemInfo before initialization");
+ }
+
+ private static ListItemInfo NotAListItem = new ListItemInfo(false, true);
+ private static ListItemInfo ZeroNumId = new ListItemInfo(false, false);
+
+ public static void InitListItemInfo(XDocument numXDoc, XDocument stylesXDoc, XElement paragraph)
+ {
+ if (FirstRunIsEmptySectionBreak(paragraph))
+ {
+ paragraph.AddAnnotation(NotAListItem);
+ return;
+ }
+
+ int? paragraphNumId = null;
+
+ XElement paragraphNumberingProperties = paragraph
+ .Elements(W.pPr)
+ .Elements(W.numPr)
+ .FirstOrDefault();
+
+ if (paragraphNumberingProperties != null)
+ {
+ paragraphNumId = (int?)paragraphNumberingProperties
+ .Elements(W.numId)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ // if numPr of paragraph does not contain numId, then it is not a list item.
+ // if numId of paragraph == 0, then this is not a list item, regardless of the markup in the style.
+ if (paragraphNumId == null || paragraphNumId == 0)
+ {
+ paragraph.AddAnnotation(NotAListItem);
+ return;
+ }
+ }
+
+ string paragraphStyleName = GetParagraphStyleName(stylesXDoc, paragraph);
+
+ var listItemInfo = GetListItemInfoFromCache(numXDoc, paragraphStyleName, paragraphNumId);
+ if (listItemInfo != null)
+ {
+ paragraph.AddAnnotation(listItemInfo);
+
+ if (listItemInfo.FromParagraph != null)
+ {
+ var para_ilvl = (int?)paragraphNumberingProperties
+ .Elements(W.ilvl)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (para_ilvl == null)
+ para_ilvl = 0;
+
+ var abstractNum = listItemInfo.FromParagraph.Main.AbstractNum;
+ var multiLevelType = (string)abstractNum.Elements(W.multiLevelType).Attributes(W.val).FirstOrDefault();
+ if (multiLevelType == "singleLevel")
+ para_ilvl = 0;
+
+ SetParagraphLevel(paragraph, (int)para_ilvl);
+ }
+ else if (listItemInfo.FromStyle != null)
+ {
+ int this_ilvl = listItemInfo.FromStyle.Style_ilvl;
+ var abstractNum = listItemInfo.FromStyle.Main.AbstractNum;
+ var multiLevelType = (string)abstractNum.Elements(W.multiLevelType).Attributes(W.val).FirstOrDefault();
+ if (multiLevelType == "singleLevel")
+ this_ilvl = 0;
+
+ SetParagraphLevel(paragraph, this_ilvl);
+ }
+ return;
+ }
+
+ listItemInfo = new ListItemInfo();
+
+ int? style_ilvl = null;
+ bool? styleZeroNumId = null;
+
+ if (paragraphStyleName != null)
+ {
+ listItemInfo.FromStyle = InitializeStyleListItemSource(numXDoc, stylesXDoc, paragraph, paragraphStyleName,
+ out style_ilvl, out styleZeroNumId);
+ }
+
+ int? paragraph_ilvl = null;
+ bool? paragraphZeroNumId = null;
+
+ if (paragraphNumberingProperties != null && paragraphNumberingProperties.Element(W.numId) != null)
+ {
+ listItemInfo.FromParagraph = InitializeParagraphListItemSource(numXDoc, stylesXDoc, paragraph, paragraphNumberingProperties, out paragraph_ilvl, out paragraphZeroNumId);
+ }
+
+ if (styleZeroNumId == true && paragraphZeroNumId == null ||
+ paragraphZeroNumId == true)
+ {
+ paragraph.AddAnnotation(NotAListItem);
+ AddListItemInfoIntoCache(numXDoc, paragraphStyleName, paragraphNumId, NotAListItem);
+ return;
+ }
+
+ int ilvlToSet = 0;
+ if (paragraph_ilvl != null)
+ ilvlToSet = (int)paragraph_ilvl;
+ else if (style_ilvl != null)
+ ilvlToSet = (int)style_ilvl;
+
+ if (listItemInfo.FromParagraph != null)
+ {
+ var abstractNum = listItemInfo.FromParagraph.Main.AbstractNum;
+ var multiLevelType = (string)abstractNum.Elements(W.multiLevelType).Attributes(W.val).FirstOrDefault();
+ if (multiLevelType == "singleLevel")
+ ilvlToSet = 0;
+ }
+ else if (listItemInfo.FromStyle != null)
+ {
+ var abstractNum = listItemInfo.FromStyle.Main.AbstractNum;
+ var multiLevelType = (string)abstractNum.Elements(W.multiLevelType).Attributes(W.val).FirstOrDefault();
+ if (multiLevelType == "singleLevel")
+ ilvlToSet = 0;
+ }
+
+ SetParagraphLevel(paragraph, ilvlToSet);
+
+ listItemInfo.IsListItem = listItemInfo.FromStyle != null || listItemInfo.FromParagraph != null;
+ paragraph.AddAnnotation(listItemInfo);
+ AddListItemInfoIntoCache(numXDoc, paragraphStyleName, paragraphNumId, listItemInfo);
+ }
+
+ private static string GetParagraphStyleName(XDocument stylesXDoc, XElement paragraph)
+ {
+ var paragraphStyleName = (string)paragraph
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (paragraphStyleName == null)
+ paragraphStyleName = GetDefaultParagraphStyleName(stylesXDoc);
+
+ return paragraphStyleName;
+ }
+
+ private static bool FirstRunIsEmptySectionBreak(XElement paragraph)
+ {
+ var firstRun = paragraph
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.r)
+ .FirstOrDefault();
+
+ var hasTextElement = paragraph
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.r)
+ .Elements(W.t)
+ .Any();
+
+ if (firstRun == null || !hasTextElement)
+ {
+ if (paragraph
+ .Elements(W.pPr)
+ .Elements(W.sectPr)
+ .Any())
+ return true;
+ }
+ return false;
+ }
+
+ private static ListItemSource InitializeParagraphListItemSource(XDocument numXDoc, XDocument stylesXDoc, XElement paragraph, XElement paragraphNumberingProperties, out int? ilvl, out bool? zeroNumId)
+ {
+ zeroNumId = null;
+
+ // Paragraph numbering properties must contain a numId.
+ int? numId = (int?)paragraphNumberingProperties
+ .Elements(W.numId)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ ilvl = (int?)paragraphNumberingProperties
+ .Elements(W.ilvl)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (numId == null)
+ {
+ zeroNumId = true;
+ return null;
+ }
+
+ var num = numXDoc
+ .Root
+ .Elements(W.num)
+ .FirstOrDefault(n => (int)n.Attribute(W.numId) == numId);
+ if (num == null)
+ {
+ zeroNumId = true;
+ return null;
+ }
+
+ zeroNumId = false;
+
+ if (ilvl == null)
+ ilvl = 0;
+
+ ListItemSource listItemSource = new ListItemSource(numXDoc, stylesXDoc, (int)numId);
+
+ return listItemSource;
+ }
+
+ private static ListItemSource InitializeStyleListItemSource(XDocument numXDoc, XDocument stylesXDoc, XElement paragraph, string paragraphStyleName,
+ out int? ilvl, out bool? zeroNumId)
+ {
+ zeroNumId = null;
+ XElement pPr = FormattingAssembler.ParagraphStyleRollup(paragraph, stylesXDoc, GetDefaultParagraphStyleName(stylesXDoc));
+ if (pPr != null)
+ {
+ XElement styleNumberingProperties = pPr
+ .Elements(W.numPr)
+ .FirstOrDefault();
+
+ if (styleNumberingProperties != null && styleNumberingProperties.Element(W.numId) != null)
+ {
+ int numId = (int)styleNumberingProperties
+ .Elements(W.numId)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ ilvl = (int?)styleNumberingProperties
+ .Elements(W.ilvl)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (ilvl == null)
+ ilvl = 0;
+
+ if (numId == 0)
+ {
+ zeroNumId = true;
+ return null;
+ }
+
+ // make sure that the numId is valid
+ XElement num = numXDoc
+ .Root
+ .Elements(W.num)
+ .Where(e => (int)e.Attribute(W.numId) == numId)
+ .FirstOrDefault();
+
+ if (num == null)
+ {
+ zeroNumId = true;
+ return null;
+ }
+
+ ListItemSource listItemSource = new ListItemSource(numXDoc, stylesXDoc, numId);
+ listItemSource.Style_ilvl = (int)ilvl;
+
+ zeroNumId = false;
+ return listItemSource;
+ }
+ }
+ ilvl = null;
+ return null;
+ }
+
+ private static string GetDefaultParagraphStyleName(XDocument stylesXDoc)
+ {
+ XElement defaultParagraphStyle;
+ string defaultParagraphStyleName = null;
+
+ StylesInfo stylesInfo = stylesXDoc.Annotation<StylesInfo>();
+
+ if (stylesInfo != null)
+ defaultParagraphStyleName = stylesInfo.DefaultParagraphStyleName;
+ else
+ {
+ defaultParagraphStyle = stylesXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s =>
+ {
+ if ((string)s.Attribute(W.type) != "paragraph")
+ return false;
+ var defaultAttribute = s.Attribute(W._default);
+ var isDefault = false;
+ if (defaultAttribute != null &&
+ (bool)s.Attribute(W._default).ToBoolean())
+ isDefault = true;
+ return isDefault;
+ });
+ defaultParagraphStyleName = null;
+ if (defaultParagraphStyle != null)
+ defaultParagraphStyleName = (string)defaultParagraphStyle.Attribute(W.styleId);
+ stylesInfo = new StylesInfo()
+ {
+ DefaultParagraphStyleName = defaultParagraphStyleName,
+ };
+ stylesXDoc.AddAnnotation(stylesInfo);
+ }
+ return defaultParagraphStyleName;
+ }
+
+ private static ListItemInfo GetListItemInfoFromCache(XDocument numXDoc, string styleName, int? numId)
+ {
+ string key =
+ (styleName == null ? "" : styleName) +
+ "|" +
+ (numId == null ? "" : numId.ToString());
+
+ var numXDocRoot = numXDoc.Root;
+ Dictionary<string, ListItemInfo> listItemInfoCache =
+ numXDocRoot.Annotation<Dictionary<string, ListItemInfo>>();
+ if (listItemInfoCache == null)
+ {
+ listItemInfoCache = new Dictionary<string, ListItemInfo>();
+ numXDocRoot.AddAnnotation(listItemInfoCache);
+ }
+ if (listItemInfoCache.ContainsKey(key))
+ return listItemInfoCache[key];
+ return null;
+ }
+
+ private static void AddListItemInfoIntoCache(XDocument numXDoc, string styleName, int? numId, ListItemInfo listItemInfo)
+ {
+ string key =
+ (styleName == null ? "" : styleName) +
+ "|" +
+ (numId == null ? "" : numId.ToString());
+
+ var numXDocRoot = numXDoc.Root;
+ Dictionary<string, ListItemInfo> listItemInfoCache =
+ numXDocRoot.Annotation<Dictionary<string, ListItemInfo>>();
+ if (listItemInfoCache == null)
+ {
+ listItemInfoCache = new Dictionary<string, ListItemInfo>();
+ numXDocRoot.AddAnnotation(listItemInfoCache);
+ }
+ if (!listItemInfoCache.ContainsKey(key))
+ listItemInfoCache.Add(key, listItemInfo);
+ }
+
+ public class LevelNumbers
+ {
+ public int[] LevelNumbersArray;
+ }
+
+ private class StylesInfo
+ {
+ public string DefaultParagraphStyleName;
+ }
+
+ private class ParagraphInfo
+ {
+ public int Ilvl;
+ }
+
+ private class ReverseAxis
+ {
+ public XElement PreviousParagraph;
+ }
+
+ public static string RetrieveListItem(WordprocessingDocument wordDoc, XElement paragraph)
+ {
+ return RetrieveListItem(wordDoc, paragraph, null);
+ }
+
+ public static string RetrieveListItem(WordprocessingDocument wordDoc, XElement paragraph, ListItemRetrieverSettings settings)
+ {
+ if (wordDoc.MainDocumentPart.NumberingDefinitionsPart == null)
+ return null;
+
+ var listItemInfo = paragraph.Annotation<ListItemInfo>();
+ if (listItemInfo == null)
+ InitializeListItemRetriever(wordDoc, settings);
+
+ listItemInfo = paragraph.Annotation<ListItemInfo>();
+ if (!listItemInfo.IsListItem)
+ return null;
+
+ var numberingDefinitionsPart = wordDoc
+ .MainDocumentPart
+ .NumberingDefinitionsPart;
+
+ if (numberingDefinitionsPart == null)
+ return null;
+
+ StyleDefinitionsPart styleDefinitionsPart = wordDoc
+ .MainDocumentPart
+ .StyleDefinitionsPart;
+
+ if (styleDefinitionsPart == null)
+ return null;
+
+ var numXDoc = numberingDefinitionsPart.GetXDocument();
+ var stylesXDoc = styleDefinitionsPart.GetXDocument();
+
+ var lvl = listItemInfo.Lvl(GetParagraphLevel(paragraph));
+
+ string lvlText = (string)lvl.Elements(W.lvlText).Attributes(W.val).FirstOrDefault();
+ if (lvlText == null)
+ return null;
+
+ var levelNumbersAnnotation = paragraph.Annotation<LevelNumbers>();
+ if (levelNumbersAnnotation == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ int[] levelNumbers = levelNumbersAnnotation.LevelNumbersArray;
+ string languageIdentifier = GetLanguageIdentifier(paragraph, stylesXDoc);
+ string listItem = FormatListItem(listItemInfo, levelNumbers, GetParagraphLevel(paragraph),
+ lvlText, stylesXDoc, languageIdentifier, settings);
+ return listItem;
+ }
+
+ private static string GetLanguageIdentifier(XElement paragraph, XDocument stylesXDoc)
+ {
+ var languageType = (string)paragraph
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.r)
+ .Attributes(PtOpenXml.LanguageType)
+ .FirstOrDefault();
+
+ string languageIdentifier = null;
+
+ if (languageType == null || languageType == "western")
+ {
+ languageIdentifier = (string)paragraph
+ .Elements(W.r)
+ .Elements(W.rPr)
+ .Elements(W.lang)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (languageIdentifier == null)
+ languageIdentifier = (string)stylesXDoc
+ .Root
+ .Elements(W.docDefaults)
+ .Elements(W.rPrDefault)
+ .Elements(W.rPr)
+ .Elements(W.lang)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ }
+ else if (languageType == "eastAsia")
+ {
+ languageIdentifier = (string)paragraph
+ .Elements(W.r)
+ .Elements(W.rPr)
+ .Elements(W.lang)
+ .Attributes(W.eastAsia)
+ .FirstOrDefault();
+
+ if (languageIdentifier == null)
+ languageIdentifier = (string)stylesXDoc
+ .Root
+ .Elements(W.docDefaults)
+ .Elements(W.rPrDefault)
+ .Elements(W.rPr)
+ .Elements(W.lang)
+ .Attributes(W.eastAsia)
+ .FirstOrDefault();
+ }
+ else if (languageType == "bidi")
+ {
+ languageIdentifier = (string)paragraph
+ .Elements(W.r)
+ .Elements(W.rPr)
+ .Elements(W.lang)
+ .Attributes(W.bidi)
+ .FirstOrDefault();
+
+ if (languageIdentifier == null)
+ languageIdentifier = (string)stylesXDoc
+ .Root
+ .Elements(W.docDefaults)
+ .Elements(W.rPrDefault)
+ .Elements(W.rPr)
+ .Elements(W.lang)
+ .Attributes(W.bidi)
+ .FirstOrDefault();
+ }
+
+
+ if (languageIdentifier == null)
+ languageIdentifier = "en-US";
+ return languageIdentifier;
+ }
+
+ private static void InitializeListItemRetriever(WordprocessingDocument wordDoc, ListItemRetrieverSettings settings)
+ {
+ foreach (var part in wordDoc.ContentParts())
+ InitializeListItemRetrieverForPart(wordDoc, part, settings);
+
+#if false
+ foreach (var part in wordDoc.ContentParts())
+ {
+ var xDoc = part.GetXDocument();
+ var paras = xDoc
+ .Descendants(W.p)
+ .Where(p =>
+ p.Annotation<ListItemInfo>() == null);
+ if (paras.Any())
+ Console.WriteLine("Error");
+ }
+#endif
+ }
+
+ private static void InitializeListItemRetrieverForPart(WordprocessingDocument wordDoc, OpenXmlPart part, ListItemRetrieverSettings settings)
+ {
+ var mainXDoc = part.GetXDocument();
+
+ var numPart = wordDoc.MainDocumentPart.NumberingDefinitionsPart;
+ if (numPart == null)
+ return;
+ var numXDoc = numPart.GetXDocument();
+
+ var stylesPart = wordDoc.MainDocumentPart.StyleDefinitionsPart;
+ if (stylesPart == null)
+ return;
+ var stylesXDoc = stylesPart.GetXDocument();
+
+ var rootNode = mainXDoc.Root;
+
+ InitializeListItemRetrieverForStory(numXDoc, stylesXDoc, rootNode);
+
+ var textBoxes = mainXDoc
+ .Root
+ .Descendants(W.txbxContent);
+
+ foreach (var textBox in textBoxes)
+ InitializeListItemRetrieverForStory(numXDoc, stylesXDoc, textBox);
+ }
+
+ private static void InitializeListItemRetrieverForStory(XDocument numXDoc, XDocument stylesXDoc, XElement rootNode)
+ {
+ var paragraphs = rootNode
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(p => p.Name == W.p);
+
+ foreach (var paragraph in paragraphs)
+ InitListItemInfo(numXDoc, stylesXDoc, paragraph);
+
+ var abstractNumIds = paragraphs
+ .Select(paragraph =>
+ {
+ ListItemInfo listItemInfo = paragraph.Annotation<ListItemInfo>();
+ if (!listItemInfo.IsListItem)
+ return (int?)null;
+ return listItemInfo.AbstractNumId;
+ })
+ .Where(a => a != null)
+ .Distinct()
+ .ToList();
+
+ // when debugging, it is sometimes useful to cause processing of a specific abstractNumId first.
+ // the following code enables this.
+ //int? abstractIdToProcessFirst = null;
+ //if (abstractIdToProcessFirst != null)
+ //{
+ // abstractNumIds = (new[] { abstractIdToProcessFirst })
+ // .Concat(abstractNumIds.Where(ani => ani != abstractIdToProcessFirst))
+ // .ToList();
+ //}
+
+ foreach (var abstractNumId in abstractNumIds)
+ {
+ var listItems = paragraphs
+ .Where(paragraph =>
+ {
+ var listItemInfo = paragraph.Annotation<ListItemInfo>();
+ if (!listItemInfo.IsListItem)
+ return false;
+ return listItemInfo.AbstractNumId == abstractNumId;
+ })
+ .ToList();
+
+ // annotate paragraphs with previous paragraphs so that we can look backwards with good perf
+ XElement prevParagraph = null;
+ foreach (var paragraph in listItems)
+ {
+ ReverseAxis reverse = new ReverseAxis()
+ {
+ PreviousParagraph = prevParagraph,
+ };
+ paragraph.AddAnnotation(reverse);
+ prevParagraph = paragraph;
+ }
+
+ var startOverrideAlreadyUsed = new List<int>();
+ List<int> previous = null;
+ ListItemInfo[] listItemInfoInEffectForStartOverride = new ListItemInfo[] {
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ };
+ foreach (var paragraph in listItems)
+ {
+ var listItemInfo = paragraph.Annotation<ListItemInfo>();
+ var ilvl = GetParagraphLevel(paragraph);
+ listItemInfoInEffectForStartOverride[ilvl] = listItemInfo;
+ ListItemInfo listItemInfoInEffect = null;
+ if (ilvl > 0)
+ listItemInfoInEffect = listItemInfoInEffectForStartOverride[ilvl - 1];
+ var levelNumbers = new List<int>();
+
+ for (int level = 0; level <= ilvl; level++)
+ {
+ var numId = listItemInfo.NumId;
+ var startOverride = listItemInfo.StartOverride(level);
+ int? inEffectStartOverride = null;
+ if (listItemInfoInEffect != null)
+ inEffectStartOverride = listItemInfoInEffect.StartOverride(level);
+
+ if (level == ilvl)
+ {
+ var lvl = listItemInfo.Lvl(ilvl);
+ var lvlRestart = (int?)lvl.Elements(W.lvlRestart).Attributes(W.val).FirstOrDefault();
+ if (lvlRestart != null)
+ {
+ var previousPara = PreviousParagraphsForLvlRestart(paragraph, (int)lvlRestart)
+ .FirstOrDefault(p =>
+ {
+ var plvl = GetParagraphLevel(p);
+ return plvl == ilvl;
+ });
+ if (previousPara != null)
+ previous = previousPara.Annotation<LevelNumbers>().LevelNumbersArray.ToList();
+ }
+ }
+
+ if (previous == null ||
+ level >= previous.Count() ||
+ (level == ilvl && startOverride != null && !startOverrideAlreadyUsed.Contains(numId)))
+ {
+ if (previous == null || level >= previous.Count())
+ {
+ var start = listItemInfo.Start(level);
+ // only look at startOverride if the level that we're examining is same as the paragraph's level.
+ if (level == ilvl)
+ {
+ if (startOverride != null && !startOverrideAlreadyUsed.Contains(numId))
+ {
+ startOverrideAlreadyUsed.Add(numId);
+ start = (int)startOverride;
+ }
+ else
+ {
+ if (startOverride != null)
+ start = (int)startOverride;
+ if (inEffectStartOverride != null && inEffectStartOverride > start)
+ start = (int)inEffectStartOverride;
+ }
+ }
+ levelNumbers.Add(start);
+ }
+ else
+ {
+ var start = listItemInfo.Start(level);
+ // only look at startOverride if the level that we're examining is same as the paragraph's level.
+ if (level == ilvl)
+ {
+ if (startOverride != null)
+ {
+ if (!startOverrideAlreadyUsed.Contains(numId))
+ {
+ startOverrideAlreadyUsed.Add(numId);
+ start = (int)startOverride;
+ }
+ }
+ }
+ levelNumbers.Add(start);
+ }
+ }
+ else
+ {
+ int? thisNumber = null;
+ if (level == ilvl)
+ {
+ if (startOverride != null)
+ {
+ if (!startOverrideAlreadyUsed.Contains(numId))
+ {
+ startOverrideAlreadyUsed.Add(numId);
+ thisNumber = (int)startOverride;
+ }
+ thisNumber = previous.ElementAt(level) + 1;
+ }
+ else
+ {
+ thisNumber = previous.ElementAt(level) + 1;
+ }
+ }
+ else
+ {
+ thisNumber = previous.ElementAt(level);
+ }
+ levelNumbers.Add((int)thisNumber);
+ }
+ }
+ var levelNumbersAnno = new LevelNumbers()
+ {
+ LevelNumbersArray = levelNumbers.ToArray()
+ };
+ paragraph.AddAnnotation(levelNumbersAnno);
+ previous = levelNumbers;
+ }
+ }
+ }
+
+ private static IEnumerable<XElement> PreviousParagraphsForLvlRestart(XElement paragraph, int ilvl)
+ {
+ var current = paragraph;
+ while (true)
+ {
+ var ra = current.Annotation<ReverseAxis>();
+ if (ra == null || ra.PreviousParagraph == null)
+ yield break;
+ var raLvl = GetParagraphLevel(ra.PreviousParagraph);
+ if (raLvl < ilvl)
+ yield break;
+ yield return ra.PreviousParagraph;
+ current = ra.PreviousParagraph;
+ }
+ }
+
+ private static string FormatListItem(ListItemInfo lii, int[] levelNumbers, int ilvl,
+ string lvlText, XDocument styles, string languageCultureName, ListItemRetrieverSettings settings)
+ {
+ string[] formatTokens = GetFormatTokens(lvlText).ToArray();
+ XElement lvl = lii.Lvl(ilvl);
+ bool isLgl = lvl.Elements(W.isLgl).Any();
+ string listItem = formatTokens.Select((t, l) =>
+ {
+ if (t.Substring(0, 1) != "%")
+ return t;
+ int indentationLevel;
+ if (!Int32.TryParse(t.Substring(1), out indentationLevel))
+ return t;
+ indentationLevel -= 1;
+ if (indentationLevel >= levelNumbers.Length)
+ indentationLevel = levelNumbers.Length - 1;
+ int levelNumber = levelNumbers[indentationLevel];
+ string levelText = null;
+ XElement rlvl = lii.Lvl(indentationLevel);
+ string numFmtForLevel = (string)rlvl.Elements(W.numFmt).Attributes(W.val).FirstOrDefault();
+ if (numFmtForLevel == null)
+ {
+ var numFmtElement = rlvl.Elements(MC.AlternateContent).Elements(MC.Choice).Elements(W.numFmt).FirstOrDefault();
+ if (numFmtElement != null && (string)numFmtElement.Attribute(W.val) == "custom")
+ numFmtForLevel = (string)numFmtElement.Attribute(W.format);
+ }
+ if (numFmtForLevel != "none")
+ {
+ if (isLgl && numFmtForLevel != "decimalZero")
+ numFmtForLevel = "decimal";
+ }
+ if (languageCultureName != null && settings != null)
+ {
+ if (settings.ListItemTextImplementations.ContainsKey(languageCultureName))
+ {
+ var impl = settings.ListItemTextImplementations[languageCultureName];
+ levelText = impl(languageCultureName, levelNumber, numFmtForLevel);
+ }
+ }
+ if (levelText == null)
+ levelText = ListItemTextGetter_Default.GetListItemText(languageCultureName, levelNumber, numFmtForLevel);
+ return levelText;
+ }).StringConcatenate();
+ return listItem;
+ }
+
+ 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;
+ }
+ }
+
+ }
+}
diff --git a/OpenXmlPowerTools/MarkupSimplifier.cs b/OpenXmlPowerTools/MarkupSimplifier.cs
new file mode 100644
index 0000000..e8644c1
--- /dev/null
+++ b/OpenXmlPowerTools/MarkupSimplifier.cs
@@ -0,0 +1,709 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 2.6.00
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Xml.Linq;
+using System.Xml.Schema;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public partial class WmlDocument
+ {
+ public WmlDocument SimplifyMarkup(SimplifyMarkupSettings settings)
+ {
+ return MarkupSimplifier.SimplifyMarkup(this, settings);
+ }
+ }
+
+ public class SimplifyMarkupSettings
+ {
+ public bool AcceptRevisions;
+ public bool NormalizeXml;
+ public bool RemoveBookmarks;
+ public bool RemoveComments;
+ public bool RemoveContentControls;
+ public bool RemoveEndAndFootNotes;
+ public bool RemoveFieldCodes;
+ public bool RemoveGoBackBookmark;
+ public bool RemoveHyperlinks;
+ public bool RemoveLastRenderedPageBreak;
+ public bool RemoveMarkupForDocumentComparison;
+ public bool RemovePermissions;
+ public bool RemoveProof;
+ public bool RemoveRsidInfo;
+ public bool RemoveSmartTags;
+ public bool RemoveSoftHyphens;
+ public bool RemoveWebHidden;
+ public bool ReplaceTabsWithSpaces;
+ }
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static class MarkupSimplifier
+ {
+ public static WmlDocument SimplifyMarkup(WmlDocument doc, SimplifyMarkupSettings settings)
+ {
+ using (var streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ {
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ SimplifyMarkup(document, settings);
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void SimplifyMarkup(WordprocessingDocument doc, SimplifyMarkupSettings settings)
+ {
+ if (settings.RemoveMarkupForDocumentComparison)
+ {
+ settings.RemoveRsidInfo = true;
+ RemoveElementsForDocumentComparison(doc);
+ }
+ if (settings.RemoveRsidInfo)
+ RemoveRsidInfoInSettings(doc);
+ if (settings.AcceptRevisions)
+ RevisionAccepter.AcceptRevisions(doc);
+ foreach (OpenXmlPart part in doc.ContentParts())
+ SimplifyMarkupForPart(part, settings);
+
+ if (doc.MainDocumentPart.StyleDefinitionsPart != null)
+ SimplifyMarkupForPart(doc.MainDocumentPart.StyleDefinitionsPart, settings);
+ if (doc.MainDocumentPart.StylesWithEffectsPart != null)
+ SimplifyMarkupForPart(doc.MainDocumentPart.StylesWithEffectsPart, settings);
+
+ if (settings.RemoveComments)
+ {
+ WordprocessingCommentsPart commentsPart = doc.MainDocumentPart.WordprocessingCommentsPart;
+ if (commentsPart != null) doc.MainDocumentPart.DeletePart(commentsPart);
+
+ WordprocessingCommentsExPart commentsExPart = doc.MainDocumentPart.WordprocessingCommentsExPart;
+ if (commentsExPart != null) doc.MainDocumentPart.DeletePart(commentsExPart);
+ }
+ }
+
+ private static void RemoveRsidInfoInSettings(WordprocessingDocument doc)
+ {
+ DocumentSettingsPart part = doc.MainDocumentPart.DocumentSettingsPart;
+ if (part == null) return;
+
+ XDocument settingsXDoc = part.GetXDocument();
+ settingsXDoc.Descendants(W.rsids).Remove();
+ part.PutXDocument();
+ }
+
+ private static void RemoveElementsForDocumentComparison(WordprocessingDocument doc)
+ {
+ OpenXmlPart part = doc.ExtendedFilePropertiesPart;
+ if (part != null)
+ {
+ XDocument appPropsXDoc = part.GetXDocument();
+ appPropsXDoc.Descendants(EP.TotalTime).Remove();
+ part.PutXDocument();
+ }
+
+ part = doc.CoreFilePropertiesPart;
+ if (part != null)
+ {
+ XDocument corePropsXDoc = part.GetXDocument();
+ corePropsXDoc.Descendants(CP.revision).Remove();
+ corePropsXDoc.Descendants(DCTERMS.created).Remove();
+ corePropsXDoc.Descendants(DCTERMS.modified).Remove();
+ part.PutXDocument();
+ }
+
+ XDocument mainXDoc = doc.MainDocumentPart.GetXDocument();
+ List<XElement> bookmarkStart = mainXDoc
+ .Descendants(W.bookmarkStart)
+ .Where(b => (string) b.Attribute(W.name) == "_GoBack")
+ .ToList();
+ foreach (XElement item in bookmarkStart)
+ {
+ IEnumerable<XElement> bookmarkEnd = mainXDoc
+ .Descendants(W.bookmarkEnd)
+ .Where(be => (int) be.Attribute(W.id) == (int) item.Attribute(W.id));
+ bookmarkEnd.Remove();
+ }
+
+ bookmarkStart.Remove();
+ doc.MainDocumentPart.PutXDocument();
+ }
+
+ public static XElement MergeAdjacentSuperfluousRuns(XElement element)
+ {
+ return (XElement) MergeAdjacentRunsTransform(element);
+ }
+
+ public static XElement TransformElementToSingleCharacterRuns(XElement element)
+ {
+ return (XElement) SingleCharacterRunTransform(element);
+ }
+
+ public static void TransformPartToSingleCharacterRuns(OpenXmlPart part)
+ {
+ // After transforming to single character runs, Rsid info will be invalid, so
+ // remove from the part.
+ XDocument xDoc = part.GetXDocument();
+ var newRoot = (XElement) RemoveRsidTransform(xDoc.Root);
+ newRoot = (XElement) SingleCharacterRunTransform(newRoot);
+ xDoc.Elements().First().ReplaceWith(newRoot);
+ part.PutXDocument();
+ }
+
+ public static void TransformToSingleCharacterRuns(WordprocessingDocument doc)
+ {
+ if (RevisionAccepter.HasTrackedRevisions(doc))
+ throw new OpenXmlPowerToolsException(
+ "Transforming a document to single character runs is not supported for " +
+ "a document with tracked revisions.");
+
+ foreach (OpenXmlPart part in doc.ContentParts())
+ TransformPartToSingleCharacterRuns(part);
+ }
+
+ private static object RemoveCustomXmlAndContentControlsTransform(
+ XNode node, SimplifyMarkupSettings simplifyMarkupSettings)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (simplifyMarkupSettings.RemoveSmartTags &&
+ element.Name == W.smartTag)
+ return element
+ .Elements()
+ .Select(e =>
+ RemoveCustomXmlAndContentControlsTransform(e,
+ simplifyMarkupSettings));
+
+ if (simplifyMarkupSettings.RemoveContentControls &&
+ element.Name == W.sdt)
+ return element
+ .Elements(W.sdtContent)
+ .Elements()
+ .Select(e =>
+ RemoveCustomXmlAndContentControlsTransform(e,
+ simplifyMarkupSettings));
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RemoveCustomXmlAndContentControlsTransform(n, simplifyMarkupSettings)));
+ }
+
+ return node;
+ }
+
+ private static object RemoveRsidTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == W.rsid)
+ return null;
+
+ return new XElement(element.Name,
+ element
+ .Attributes()
+ .Where(a => (a.Name != W.rsid) &&
+ (a.Name != W.rsidDel) &&
+ (a.Name != W.rsidP) &&
+ (a.Name != W.rsidR) &&
+ (a.Name != W.rsidRDefault) &&
+ (a.Name != W.rsidRPr) &&
+ (a.Name != W.rsidSect) &&
+ (a.Name != W.rsidTr)),
+ element.Nodes().Select(n => RemoveRsidTransform(n)));
+ }
+
+ private static object MergeAdjacentRunsTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == W.p)
+ return WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(element);
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => MergeAdjacentRunsTransform(n)));
+ }
+
+ private static object RemoveEmptyRunsAndRunPropertiesTransform(
+ XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (((element.Name == W.r) || (element.Name == W.rPr) || (element.Name == W.pPr)) &&
+ !element.Elements().Any())
+ return null;
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RemoveEmptyRunsAndRunPropertiesTransform(n)));
+ }
+
+ return node;
+ }
+
+ private static object MergeAdjacentInstrText(
+ XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if ((element.Name == W.r) && element.Elements(W.instrText).Any())
+ {
+ IEnumerable<IGrouping<bool, XElement>> grouped =
+ element.Elements().GroupAdjacent(e => e.Name == W.instrText);
+ return new XElement(W.r,
+ grouped.Select(g =>
+ {
+ if (g.Key == false)
+ return (object) g;
+
+ // If .doc files are converted to .docx by the Binary to Open XML Translator,
+ // the w:instrText elements might be empty, in which case newInstrText would
+ // be an empty string.
+ string newInstrText = g.Select(i => (string) i).StringConcatenate();
+ if (string.IsNullOrEmpty(newInstrText))
+ return new XElement(W.instrText);
+
+ return new XElement(W.instrText,
+ (newInstrText[0] == ' ') || (newInstrText[newInstrText.Length - 1] == ' ')
+ ? new XAttribute(XNamespace.Xml + "space", "preserve")
+ : null,
+ newInstrText);
+ }));
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => MergeAdjacentInstrText(n)));
+ }
+
+ return node;
+ }
+
+ // lastRenderedPageBreak, permEnd, permStart, proofErr, noProof
+ // softHyphen:
+ // Remove when simplifying.
+
+ // fldSimple, fldData, fldChar, instrText:
+ // For hyperlinks, generate same in XHtml. Other than hyperlinks, do the following:
+ // - collapse fldSimple
+ // - remove fldSimple, fldData, fldChar, instrText.
+
+ private static object SimplifyMarkupTransform(
+ XNode node,
+ SimplifyMarkupSettings settings,
+ SimplifyMarkupParameters parameters)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (settings.RemovePermissions &&
+ ((element.Name == W.permEnd) ||
+ (element.Name == W.permStart)))
+ return null;
+
+ if (settings.RemoveProof &&
+ ((element.Name == W.proofErr) ||
+ (element.Name == W.noProof)))
+ return null;
+
+ if (settings.RemoveSoftHyphens &&
+ (element.Name == W.softHyphen))
+ return null;
+
+ if (settings.RemoveLastRenderedPageBreak &&
+ (element.Name == W.lastRenderedPageBreak))
+ return null;
+
+ if (settings.RemoveBookmarks &&
+ ((element.Name == W.bookmarkStart) ||
+ (element.Name == W.bookmarkEnd)))
+ return null;
+
+ if (settings.RemoveGoBackBookmark &&
+ (((element.Name == W.bookmarkStart) && ((int) element.Attribute(W.id) == parameters.GoBackId)) ||
+ ((element.Name == W.bookmarkEnd) && ((int) element.Attribute(W.id) == parameters.GoBackId))))
+ return null;
+
+ if (settings.RemoveWebHidden &&
+ (element.Name == W.webHidden))
+ return null;
+
+ if (settings.ReplaceTabsWithSpaces &&
+ (element.Name == W.tab) &&
+ (element.Parent != null && element.Parent.Name == W.r))
+ return new XElement(W.t, new XAttribute(XNamespace.Xml + "space", "preserve"), " ");
+
+ if (settings.RemoveComments &&
+ ((element.Name == W.commentRangeStart) ||
+ (element.Name == W.commentRangeEnd) ||
+ (element.Name == W.commentReference) ||
+ (element.Name == W.annotationRef)))
+ return null;
+
+ if (settings.RemoveComments &&
+ (element.Name == W.rStyle) &&
+ (element.Attribute(W.val).Value == "CommentReference"))
+ return null;
+
+ if (settings.RemoveEndAndFootNotes &&
+ ((element.Name == W.endnoteReference) ||
+ (element.Name == W.footnoteReference)))
+ return null;
+
+ if (settings.RemoveFieldCodes)
+ {
+ if (element.Name == W.fldSimple)
+ return element.Elements().Select(e => SimplifyMarkupTransform(e, settings, parameters));
+
+ if ((element.Name == W.fldData) ||
+ (element.Name == W.fldChar) ||
+ (element.Name == W.instrText))
+ return null;
+ }
+
+ if (settings.RemoveHyperlinks &&
+ (element.Name == W.hyperlink))
+ return element.Elements();
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => SimplifyMarkupTransform(n, settings, parameters)));
+ }
+
+ private static XDocument Normalize(XDocument source, XmlSchemaSet schema)
+ {
+ var havePsvi = false;
+
+ // validate, throw errors, add PSVI information
+ if (schema != null)
+ {
+ source.Validate(schema, null, true);
+ havePsvi = true;
+ }
+ return new XDocument(
+ source.Declaration,
+ source.Nodes().Select(n =>
+ {
+ // Remove comments, processing instructions, and text nodes that are
+ // children of XDocument. Only white space text nodes are allowed as
+ // children of a document, so we can remove all text nodes.
+ if (n is XComment || n is XProcessingInstruction || n is XText)
+ return null;
+
+ var e = n as XElement;
+ return e != null ? NormalizeElement(e, havePsvi) : n;
+ }));
+ }
+
+ // TODO: Check whether this can be removed.
+ //private static bool DeepEqualsWithNormalization(XDocument doc1, XDocument doc2, XmlSchemaSet schemaSet)
+ //{
+ // XDocument d1 = Normalize(doc1, schemaSet);
+ // XDocument d2 = Normalize(doc2, schemaSet);
+ // return XNode.DeepEquals(d1, d2);
+ //}
+
+ private static IEnumerable<XAttribute> NormalizeAttributes(XElement element, bool havePsvi)
+ {
+ return element.Attributes()
+ .Where(a => !a.IsNamespaceDeclaration &&
+ (a.Name != Xsi.schemaLocation) &&
+ (a.Name != Xsi.noNamespaceSchemaLocation))
+ .OrderBy(a => a.Name.NamespaceName)
+ .ThenBy(a => a.Name.LocalName)
+ .Select(a =>
+ {
+ if (havePsvi)
+ {
+ IXmlSchemaInfo schemaInfo = a.GetSchemaInfo();
+ XmlSchemaType schemaType = schemaInfo != null ? schemaInfo.SchemaType : null;
+ XmlTypeCode? typeCode = schemaType != null ? schemaType.TypeCode : (XmlTypeCode?) null;
+
+ switch (typeCode)
+ {
+ case XmlTypeCode.Boolean:
+ return new XAttribute(a.Name, (bool) a);
+ case XmlTypeCode.DateTime:
+ return new XAttribute(a.Name, (DateTime) a);
+ case XmlTypeCode.Decimal:
+ return new XAttribute(a.Name, (decimal) a);
+ case XmlTypeCode.Double:
+ return new XAttribute(a.Name, (double) a);
+ case XmlTypeCode.Float:
+ return new XAttribute(a.Name, (float) a);
+ case XmlTypeCode.HexBinary:
+ case XmlTypeCode.Language:
+ return new XAttribute(a.Name,
+ ((string) a).ToLower());
+ }
+ }
+
+ return a;
+ });
+ }
+
+ private static XNode NormalizeNode(XNode node, bool havePsvi)
+ {
+ // trim comments and processing instructions from normalized tree
+ if (node is XComment || node is XProcessingInstruction)
+ return null;
+
+ var e = node as XElement;
+ if (e != null)
+ return NormalizeElement(e, havePsvi);
+
+ // Only thing left is XCData and XText, so clone them
+ return node;
+ }
+
+ private static XElement NormalizeElement(XElement element, bool havePsvi)
+ {
+ if (havePsvi)
+ {
+ IXmlSchemaInfo schemaInfo = element.GetSchemaInfo();
+ XmlSchemaType schemaType = schemaInfo != null ? schemaInfo.SchemaType : null;
+ XmlTypeCode? typeCode = schemaType != null ? schemaType.TypeCode : (XmlTypeCode?) null;
+
+ switch (typeCode)
+ {
+ case XmlTypeCode.Boolean:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ (bool) element);
+ case XmlTypeCode.DateTime:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ (DateTime) element);
+ case XmlTypeCode.Decimal:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ (decimal) element);
+ case XmlTypeCode.Double:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ (double) element);
+ case XmlTypeCode.Float:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ (float) element);
+ case XmlTypeCode.HexBinary:
+ case XmlTypeCode.Language:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ ((string) element).ToLower());
+ default:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, true),
+ element.Nodes().Select(n => NormalizeNode(n, true)));
+ }
+ }
+
+ return new XElement(element.Name,
+ NormalizeAttributes(element, false),
+ element.Nodes().Select(n => NormalizeNode(n, false)));
+ }
+
+ private static void SimplifyMarkupForPart(OpenXmlPart part, SimplifyMarkupSettings settings)
+ {
+ var parameters = new SimplifyMarkupParameters();
+ if (part.ContentType == "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml")
+ {
+ var doc = (WordprocessingDocument) part.OpenXmlPackage;
+ if (settings.RemoveGoBackBookmark)
+ {
+ XElement goBackBookmark = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Descendants(W.bookmarkStart)
+ .FirstOrDefault(bm => (string) bm.Attribute(W.name) == "_GoBack");
+ if (goBackBookmark != null)
+ parameters.GoBackId = (int) goBackBookmark.Attribute(W.id);
+ }
+ }
+
+ XDocument xdoc = part.GetXDocument();
+ XElement newRoot = xdoc.Root;
+
+ // Need to do this first to enable simplifying hyperlinks.
+ if (settings.RemoveContentControls || settings.RemoveSmartTags)
+ newRoot = (XElement) RemoveCustomXmlAndContentControlsTransform(newRoot, settings);
+
+ // This may touch many elements, so needs to be its own transform.
+ if (settings.RemoveRsidInfo)
+ newRoot = (XElement) RemoveRsidTransform(newRoot);
+
+ var prevNewRoot = new XDocument(newRoot);
+ while (true)
+ {
+ if (settings.RemoveComments ||
+ settings.RemoveEndAndFootNotes ||
+ settings.ReplaceTabsWithSpaces ||
+ settings.RemoveFieldCodes ||
+ settings.RemovePermissions ||
+ settings.RemoveProof ||
+ settings.RemoveBookmarks ||
+ settings.RemoveWebHidden ||
+ settings.RemoveGoBackBookmark ||
+ settings.RemoveHyperlinks)
+ newRoot = (XElement) SimplifyMarkupTransform(newRoot, settings, parameters);
+
+ // Remove runs and run properties that have become empty due to previous transforms.
+ newRoot = (XElement) RemoveEmptyRunsAndRunPropertiesTransform(newRoot);
+
+ // Merge adjacent runs that have identical run properties.
+ newRoot = (XElement) MergeAdjacentRunsTransform(newRoot);
+
+ // Merge adjacent instrText elements.
+ newRoot = (XElement) MergeAdjacentInstrText(newRoot);
+
+ // Separate run children into separate runs
+ newRoot = (XElement) SeparateRunChildrenIntoSeparateRuns(newRoot);
+
+ if (XNode.DeepEquals(prevNewRoot.Root, newRoot))
+ break;
+
+ prevNewRoot = new XDocument(newRoot);
+ }
+
+ if (settings.NormalizeXml)
+ {
+ XAttribute[] nsAttrs =
+ {
+ new XAttribute(XNamespace.Xmlns + "wpc", WPC.wpc),
+ new XAttribute(XNamespace.Xmlns + "mc", MC.mc),
+ new XAttribute(XNamespace.Xmlns + "o", O.o),
+ new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XAttribute(XNamespace.Xmlns + "m", M.m),
+ new XAttribute(XNamespace.Xmlns + "v", VML.vml),
+ new XAttribute(XNamespace.Xmlns + "wp14", WP14.wp14),
+ new XAttribute(XNamespace.Xmlns + "wp", WP.wp),
+ new XAttribute(XNamespace.Xmlns + "w10", W10.w10),
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(XNamespace.Xmlns + "w14", W14.w14),
+ new XAttribute(XNamespace.Xmlns + "w15", W15.w15),
+ new XAttribute(XNamespace.Xmlns + "w16se", W16SE.w16se),
+ new XAttribute(XNamespace.Xmlns + "wpg", WPG.wpg),
+ new XAttribute(XNamespace.Xmlns + "wpi", WPI.wpi),
+ new XAttribute(XNamespace.Xmlns + "wne", WNE.wne),
+ new XAttribute(XNamespace.Xmlns + "wps", WPS.wps),
+ new XAttribute(MC.Ignorable, "w14 wp14 w15 w16se"),
+ };
+
+ XDocument newXDoc = Normalize(new XDocument(newRoot), null);
+ newRoot = newXDoc.Root;
+ if (newRoot != null)
+ foreach (XAttribute nsAttr in nsAttrs)
+ if (newRoot.Attribute(nsAttr.Name) == null)
+ newRoot.Add(nsAttr);
+
+ part.PutXDocument(newXDoc);
+ }
+ else
+ {
+ part.PutXDocument(new XDocument(newRoot));
+ }
+ }
+
+ private static object SeparateRunChildrenIntoSeparateRuns(XNode node)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == W.r)
+ {
+ IEnumerable<XElement> runChildren = element.Elements().Where(e => e.Name != W.rPr);
+ XElement rPr = element.Element(W.rPr);
+ return runChildren.Select(rc => new XElement(W.r, rPr, rc));
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => SeparateRunChildrenIntoSeparateRuns(n)));
+ }
+
+ private static object SingleCharacterRunTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == W.r)
+ return element.Elements()
+ .Where(e => e.Name != W.rPr)
+ .GroupAdjacent(sr => sr.Name == W.t)
+ .Select(g =>
+ {
+ if (g.Key)
+ {
+ string s = g.Select(t => (string) t).StringConcatenate();
+ return s.Select(c =>
+ new XElement(W.r,
+ element.Elements(W.rPr),
+ new XElement(W.t,
+ c == ' ' ? new XAttribute(XNamespace.Xml + "space", "preserve") : null,
+ c)));
+ }
+
+ return g.Select(sr =>
+ new XElement(W.r,
+ element.Elements(W.rPr),
+ new XElement(sr.Name,
+ sr.Attributes(),
+ sr.Nodes().Select(n => SingleCharacterRunTransform(n)))));
+ });
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => SingleCharacterRunTransform(n)));
+ }
+
+ private static class Xsi
+ {
+ private static readonly XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
+
+ public static readonly XName schemaLocation = xsi + "schemaLocation";
+ public static readonly XName noNamespaceSchemaLocation = xsi + "noNamespaceSchemaLocation";
+ }
+
+ public class InternalException : Exception
+ {
+ public InternalException(string message) : base(message)
+ {
+ }
+ }
+
+ public class InvalidSettingsException : Exception
+ {
+ public InvalidSettingsException(string message) : base(message)
+ {
+ }
+ }
+
+ private class SimplifyMarkupParameters
+ {
+ public int? GoBackId { get; set; }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/MetricsGetter.cs b/OpenXmlPowerTools/MetricsGetter.cs
new file mode 100644
index 0000000..27ef88a
--- /dev/null
+++ b/OpenXmlPowerTools/MetricsGetter.cs
@@ -0,0 +1,1026 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Packaging;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using System.Globalization;
+
+namespace OpenXmlPowerTools
+{
+ public class MetricsGetterSettings
+ {
+ public bool IncludeTextInContentControls;
+ public bool IncludeXlsxTableCellData;
+ public bool RetrieveNamespaceList;
+ public bool RetrieveContentTypeList;
+ }
+
+ public class MetricsGetter
+ {
+ public static XElement GetMetrics(string fileName, MetricsGetterSettings settings)
+ {
+ FileInfo fi = new FileInfo(fileName);
+ if (!fi.Exists)
+ throw new FileNotFoundException("{0} does not exist.", fi.FullName);
+ if (Util.IsWordprocessingML(fi.Extension))
+ {
+ WmlDocument wmlDoc = new WmlDocument(fi.FullName, true);
+ return GetDocxMetrics(wmlDoc, settings);
+ }
+ if (Util.IsSpreadsheetML(fi.Extension))
+ {
+ SmlDocument smlDoc = new SmlDocument(fi.FullName, true);
+ return GetXlsxMetrics(smlDoc, settings);
+ }
+ if (Util.IsPresentationML(fi.Extension))
+ {
+ PmlDocument pmlDoc = new PmlDocument(fi.FullName, true);
+ return GetPptxMetrics(pmlDoc, settings);
+ }
+ return null;
+ }
+
+ public static XElement GetDocxMetrics(WmlDocument wmlDoc, MetricsGetterSettings settings)
+ {
+ try
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlDoc.DocumentByteArray, 0, wmlDoc.DocumentByteArray.Length);
+ using (WordprocessingDocument document = WordprocessingDocument.Open(ms, true))
+ {
+ bool hasTrackedRevisions = RevisionAccepter.HasTrackedRevisions(document);
+ if (hasTrackedRevisions)
+ RevisionAccepter.AcceptRevisions(document);
+ XElement metrics1 = GetWmlMetrics(wmlDoc.FileName, false, document, settings);
+ if (hasTrackedRevisions)
+ metrics1.Add(new XElement(H.RevisionTracking, new XAttribute(H.Val, true)));
+ return metrics1;
+ }
+ }
+ }
+ catch (OpenXmlPowerToolsException e)
+ {
+ if (e.ToString().Contains("Invalid Hyperlink"))
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlDoc.DocumentByteArray, 0, wmlDoc.DocumentByteArray.Length);
+#if !NET35
+ UriFixer.FixInvalidUri(ms, brokenUri => FixUri(brokenUri));
+#endif
+ wmlDoc = new WmlDocument("dummy.docx", ms.ToArray());
+ }
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlDoc.DocumentByteArray, 0, wmlDoc.DocumentByteArray.Length);
+ using (WordprocessingDocument document = WordprocessingDocument.Open(ms, true))
+ {
+ bool hasTrackedRevisions = RevisionAccepter.HasTrackedRevisions(document);
+ if (hasTrackedRevisions)
+ RevisionAccepter.AcceptRevisions(document);
+ XElement metrics2 = GetWmlMetrics(wmlDoc.FileName, true, document, settings);
+ if (hasTrackedRevisions)
+ metrics2.Add(new XElement(H.RevisionTracking, new XAttribute(H.Val, true)));
+ return metrics2;
+ }
+ }
+ }
+ }
+ var metrics = new XElement(H.Metrics,
+ new XAttribute(H.FileName, wmlDoc.FileName),
+ new XAttribute(H.FileType, "WordprocessingML"),
+ new XAttribute(H.Error, "Unknown error, metrics not determined"));
+ return metrics;
+ }
+
+ private static Uri FixUri(string brokenUri)
+ {
+ return new Uri("http://broken-link/");
+ }
+
+ private static XElement GetWmlMetrics(string fileName, bool invalidHyperlink, WordprocessingDocument wDoc, MetricsGetterSettings settings)
+ {
+ var parts = new XElement(H.Parts,
+ wDoc.GetAllParts().Select(part =>
+ {
+ return GetMetricsForWmlPart(part, settings);
+ }));
+ if (!parts.HasElements)
+ parts = null;
+ var metrics = new XElement(H.Metrics,
+ new XAttribute(H.FileName, fileName),
+ new XAttribute(H.FileType, "WordprocessingML"),
+ GetStyleHierarchy(wDoc),
+ GetMiscWmlMetrics(wDoc, invalidHyperlink),
+ parts,
+ settings.RetrieveNamespaceList ? RetrieveNamespaceList(wDoc) : null,
+ settings.RetrieveContentTypeList ? RetrieveContentTypeList(wDoc) : null
+ );
+ return metrics;
+ }
+
+ private static XElement RetrieveContentTypeList(OpenXmlPackage oxPkg)
+ {
+ Package pkg = oxPkg.Package;
+
+ var nonRelationshipParts = pkg.GetParts().Cast<ZipPackagePart>().Where(p => p.ContentType != "application/vnd.openxmlformats-package.relationships+xml");
+ var contentTypes = nonRelationshipParts
+ .Select(p => p.ContentType)
+ .OrderBy(t => t)
+ .Distinct();
+ var xe = new XElement(H.ContentTypes,
+ contentTypes.Select(ct => new XElement(H.ContentType, new XAttribute(H.Val, ct))));
+ return xe;
+ }
+
+ private static XElement RetrieveNamespaceList(OpenXmlPackage oxPkg)
+ {
+ Package pkg = oxPkg.Package;
+
+ var nonRelationshipParts = pkg.GetParts().Cast<ZipPackagePart>().Where(p => p.ContentType != "application/vnd.openxmlformats-package.relationships+xml");
+ var xmlParts = nonRelationshipParts
+ .Where(p => p.ContentType.ToLower().EndsWith("xml"));
+
+ var uniqueNamespaces = new HashSet<string>();
+ foreach (var xp in xmlParts)
+ {
+ using (Stream st = xp.GetStream())
+ {
+ try
+ {
+ XDocument xdoc = XDocument.Load(st);
+ var namespaces = xdoc
+ .Descendants()
+ .Attributes()
+ .Where(a => a.IsNamespaceDeclaration)
+ .Select(a => string.Format("{0}|{1}", a.Name.LocalName, a.Value))
+ .OrderBy(t => t)
+ .Distinct()
+ .ToList();
+ foreach (var item in namespaces)
+ uniqueNamespaces.Add(item);
+ }
+ // if catch exception, forget about it. Just trying to get a most complete survey possible of all namespaces in all documents.
+ // if caught exception, chances are the document is bad anyway.
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ }
+ var xe = new XElement(H.Namespaces,
+ uniqueNamespaces.OrderBy(t => t).Select(n =>
+ {
+ var spl = n.Split('|');
+ return new XElement(H.Namespace,
+ new XAttribute(H.NamespacePrefix, spl[0]),
+ new XAttribute(H.NamespaceName, spl[1]));
+ }));
+ return xe;
+ }
+
+ private static List<XElement> GetMiscWmlMetrics(WordprocessingDocument document, bool invalidHyperlink)
+ {
+ List<XElement> metrics = new List<XElement>();
+ List<string> notes = new List<string>();
+ Dictionary<XName, int> elementCountDictionary = new Dictionary<XName, int>();
+
+ if (invalidHyperlink)
+ metrics.Add(new XElement(H.InvalidHyperlink, new XAttribute(H.Val, invalidHyperlink)));
+
+ bool valid = ValidateWordprocessingDocument(document, metrics, notes, elementCountDictionary);
+ if (invalidHyperlink)
+ valid = false;
+
+ return metrics;
+ }
+
+ private static bool ValidateWordprocessingDocument(WordprocessingDocument wDoc, List<XElement> metrics, List<string> notes, Dictionary<XName, int> metricCountDictionary)
+ {
+ bool valid = ValidateAgainstSpecificVersion(wDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2007, H.SdkValidationError2007);
+ valid |= ValidateAgainstSpecificVersion(wDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2010, H.SdkValidationError2010);
+#if !NET35
+ valid |= ValidateAgainstSpecificVersion(wDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2013, H.SdkValidationError2013);
+#endif
+
+ int elementCount = 0;
+ int paragraphCount = 0;
+ int textCount = 0;
+ foreach (var part in wDoc.ContentParts())
+ {
+ XDocument xDoc = part.GetXDocument();
+ foreach (var e in xDoc.Descendants())
+ {
+ if (e.Name == W.txbxContent)
+ IncrementMetric(metricCountDictionary, H.TextBox);
+ else if (e.Name == W.sdt)
+ IncrementMetric(metricCountDictionary, H.ContentControl);
+ else if (e.Name == W.customXml)
+ IncrementMetric(metricCountDictionary, H.CustomXmlMarkup);
+ else if (e.Name == W.fldChar)
+ IncrementMetric(metricCountDictionary, H.ComplexField);
+ else if (e.Name == W.fldSimple)
+ IncrementMetric(metricCountDictionary, H.SimpleField);
+ else if (e.Name == W.altChunk)
+ IncrementMetric(metricCountDictionary, H.AltChunk);
+ else if (e.Name == W.tbl)
+ IncrementMetric(metricCountDictionary, H.Table);
+ else if (e.Name == W.hyperlink)
+ IncrementMetric(metricCountDictionary, H.Hyperlink);
+ else if (e.Name == W.framePr)
+ IncrementMetric(metricCountDictionary, H.LegacyFrame);
+ else if (e.Name == W.control)
+ IncrementMetric(metricCountDictionary, H.ActiveX);
+ else if (e.Name == W.subDoc)
+ IncrementMetric(metricCountDictionary, H.SubDocument);
+ else if (e.Name == VML.imagedata || e.Name == VML.fill || e.Name == VML.stroke || e.Name == A.blip)
+ {
+ var relId = (string)e.Attribute(R.embed);
+ if (relId != null)
+ ValidateImageExists(part, relId, metricCountDictionary);
+ relId = (string)e.Attribute(R.pict);
+ if (relId != null)
+ ValidateImageExists(part, relId, metricCountDictionary);
+ relId = (string)e.Attribute(R.id);
+ if (relId != null)
+ ValidateImageExists(part, relId, metricCountDictionary);
+ }
+
+ if (part.Uri == wDoc.MainDocumentPart.Uri)
+ {
+ elementCount++;
+ if (e.Name == W.p)
+ paragraphCount++;
+ if (e.Name == W.t)
+ textCount += ((string)e).Length;
+ }
+ }
+ }
+
+ foreach (var item in metricCountDictionary)
+ {
+ metrics.Add(
+ new XElement(item.Key, new XAttribute(H.Val, item.Value)));
+ }
+
+ metrics.Add(new XElement(H.ElementCount, new XAttribute(H.Val, elementCount)));
+ metrics.Add(new XElement(H.AverageParagraphLength, new XAttribute(H.Val, (int)((double)textCount / (double)paragraphCount))));
+
+ if (wDoc.GetAllParts().Any(part => part.ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
+ metrics.Add(new XElement(H.EmbeddedXlsx, new XAttribute(H.Val, true)));
+
+ NumberingFormatListAssembly(wDoc, metrics);
+
+ XDocument wxDoc = wDoc.MainDocumentPart.GetXDocument();
+
+ foreach (var d in wxDoc.Descendants())
+ {
+ if (d.Name == W.saveThroughXslt)
+ {
+ string rid = (string)d.Attribute(R.id);
+ var tempExternalRelationship = wDoc
+ .MainDocumentPart
+ .DocumentSettingsPart
+ .ExternalRelationships
+ .FirstOrDefault(h => h.Id == rid);
+ if (tempExternalRelationship == null)
+ metrics.Add(new XElement(H.InvalidSaveThroughXslt, new XAttribute(H.Val, true)));
+ valid = false;
+ }
+ else if (d.Name == W.trackRevisions)
+ metrics.Add(new XElement(H.TrackRevisionsEnabled, new XAttribute(H.Val, true)));
+ else if (d.Name == W.documentProtection)
+ metrics.Add(new XElement(H.DocumentProtection, new XAttribute(H.Val, true)));
+ }
+
+ FontAndCharSetAnalysis(wDoc, metrics, notes);
+
+ return valid;
+ }
+
+ private static bool ValidateAgainstSpecificVersion(WordprocessingDocument wDoc, List<XElement> metrics, DocumentFormat.OpenXml.FileFormatVersions versionToValidateAgainst, XName versionSpecificMetricName)
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(versionToValidateAgainst);
+ var errors = validator.Validate(wDoc);
+ bool valid = errors.Count() == 0;
+ if (!valid)
+ {
+ if (!metrics.Any(e => e.Name == H.SdkValidationError))
+ metrics.Add(new XElement(H.SdkValidationError, new XAttribute(H.Val, true)));
+ metrics.Add(new XElement(versionSpecificMetricName, new XAttribute(H.Val, true),
+ errors.Take(3).Select(err =>
+ {
+ StringBuilder sb = new StringBuilder();
+ if (err.Description.Length > 300)
+ sb.Append(PtUtils.MakeValidXml(err.Description.Substring(0, 300) + " ... elided ...") + Environment.NewLine);
+ else
+ sb.Append(PtUtils.MakeValidXml(err.Description) + Environment.NewLine);
+ sb.Append(" in part " + PtUtils.MakeValidXml(err.Part.Uri.ToString()) + Environment.NewLine);
+ sb.Append(" at " + PtUtils.MakeValidXml(err.Path.XPath) + Environment.NewLine);
+ return sb.ToString();
+ })));
+ }
+ return valid;
+ }
+
+ private static bool ValidateAgainstSpecificVersion(SpreadsheetDocument sDoc, List<XElement> metrics, DocumentFormat.OpenXml.FileFormatVersions versionToValidateAgainst, XName versionSpecificMetricName)
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(versionToValidateAgainst);
+ var errors = validator.Validate(sDoc);
+ bool valid = errors.Count() == 0;
+ if (!valid)
+ {
+ if (!metrics.Any(e => e.Name == H.SdkValidationError))
+ metrics.Add(new XElement(H.SdkValidationError, new XAttribute(H.Val, true)));
+ metrics.Add(new XElement(versionSpecificMetricName, new XAttribute(H.Val, true),
+ errors.Take(3).Select(err =>
+ {
+ StringBuilder sb = new StringBuilder();
+ if (err.Description.Length > 300)
+ sb.Append(PtUtils.MakeValidXml(err.Description.Substring(0, 300) + " ... elided ...") + Environment.NewLine);
+ else
+ sb.Append(PtUtils.MakeValidXml(err.Description) + Environment.NewLine);
+ sb.Append(" in part " + PtUtils.MakeValidXml(err.Part.Uri.ToString()) + Environment.NewLine);
+ sb.Append(" at " + PtUtils.MakeValidXml(err.Path.XPath) + Environment.NewLine);
+ return sb.ToString();
+ })));
+ }
+ return valid;
+ }
+
+ private static bool ValidateAgainstSpecificVersion(PresentationDocument pDoc, List<XElement> metrics, DocumentFormat.OpenXml.FileFormatVersions versionToValidateAgainst, XName versionSpecificMetricName)
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(versionToValidateAgainst);
+ var errors = validator.Validate(pDoc);
+ bool valid = errors.Count() == 0;
+ if (!valid)
+ {
+ if (!metrics.Any(e => e.Name == H.SdkValidationError))
+ metrics.Add(new XElement(H.SdkValidationError, new XAttribute(H.Val, true)));
+ metrics.Add(new XElement(versionSpecificMetricName, new XAttribute(H.Val, true),
+ errors.Take(3).Select(err =>
+ {
+ StringBuilder sb = new StringBuilder();
+ if (err.Description.Length > 300)
+ sb.Append(PtUtils.MakeValidXml(err.Description.Substring(0, 300) + " ... elided ...") + Environment.NewLine);
+ else
+ sb.Append(PtUtils.MakeValidXml(err.Description) + Environment.NewLine);
+ sb.Append(" in part " + PtUtils.MakeValidXml(err.Part.Uri.ToString()) + Environment.NewLine);
+ sb.Append(" at " + PtUtils.MakeValidXml(err.Path.XPath) + Environment.NewLine);
+ return sb.ToString();
+ })));
+ }
+ return valid;
+ }
+
+ private static void IncrementMetric(Dictionary<XName, int> metricCountDictionary, XName xName)
+ {
+ if (metricCountDictionary.ContainsKey(xName))
+ metricCountDictionary[xName] = metricCountDictionary[xName] + 1;
+ else
+ metricCountDictionary.Add(xName, 1);
+ }
+
+ private static void ValidateImageExists(OpenXmlPart part, string relId, Dictionary<XName, int> metrics)
+ {
+ var imagePart = part.Parts.FirstOrDefault(ipp => ipp.RelationshipId == relId);
+ if (imagePart == null)
+ IncrementMetric(metrics, H.ReferenceToNullImage);
+ }
+
+
+ private static void NumberingFormatListAssembly(WordprocessingDocument wDoc, List<XElement> metrics)
+ {
+ List<string> numFmtList = new List<string>();
+ foreach (var part in wDoc.ContentParts())
+ {
+ var xDoc = part.GetXDocument();
+ numFmtList = numFmtList.Concat(xDoc
+ .Descendants(W.p)
+ .Select(p =>
+ {
+ ListItemRetriever.RetrieveListItem(wDoc, p, null);
+ ListItemRetriever.ListItemInfo lif = p.Annotation<ListItemRetriever.ListItemInfo>();
+ if (lif != null && lif.IsListItem && lif.Lvl(ListItemRetriever.GetParagraphLevel(p)) != null)
+ {
+ string numFmtForLevel = (string)lif.Lvl(ListItemRetriever.GetParagraphLevel(p)).Elements(W.numFmt).Attributes(W.val).FirstOrDefault();
+ if (numFmtForLevel == null)
+ {
+ var numFmtElement = lif.Lvl(ListItemRetriever.GetParagraphLevel(p)).Elements(MC.AlternateContent).Elements(MC.Choice).Elements(W.numFmt).FirstOrDefault();
+ if (numFmtElement != null && (string)numFmtElement.Attribute(W.val) == "custom")
+ numFmtForLevel = (string)numFmtElement.Attribute(W.format);
+ }
+ return numFmtForLevel;
+ }
+ return null;
+ })
+ .Where(s => s != null)
+ .Distinct())
+ .ToList();
+ }
+ if (numFmtList.Any())
+ {
+ var nfls = numFmtList.StringConcatenate(s => s + ",").TrimEnd(',');
+ metrics.Add(new XElement(H.NumberingFormatList, new XAttribute(H.Val, PtUtils.MakeValidXml(nfls))));
+ }
+ }
+
+ class FormattingMetrics
+ {
+ public int RunCount;
+ public int RunWithoutRprCount;
+ public int ZeroLengthText;
+ public int MultiFontRun;
+
+ public int AsciiCharCount;
+ public int CSCharCount;
+ public int EastAsiaCharCount;
+ public int HAnsiCharCount;
+
+ public int AsciiRunCount;
+ public int CSRunCount;
+ public int EastAsiaRunCount;
+ public int HAnsiRunCount;
+
+ public List<string> Languages;
+
+ public FormattingMetrics()
+ {
+ Languages = new List<string>();
+ }
+ }
+
+ private static void FontAndCharSetAnalysis(WordprocessingDocument wDoc, List<XElement> metrics, List<string> notes)
+ {
+ FormattingAssemblerSettings settings = new FormattingAssemblerSettings
+ {
+ RemoveStyleNamesFromParagraphAndRunProperties = false,
+ ClearStyles = true,
+ RestrictToSupportedNumberingFormats = false,
+ RestrictToSupportedLanguages = false,
+ };
+ FormattingAssembler.AssembleFormatting(wDoc, settings);
+ var formattingMetrics = new FormattingMetrics();
+
+ foreach (var part in wDoc.ContentParts())
+ {
+ var xDoc = part.GetXDocument();
+ foreach (var run in xDoc.Descendants(W.r))
+ {
+ formattingMetrics.RunCount++;
+ AnalyzeRun(run, metrics, notes, formattingMetrics, part.Uri.ToString());
+ }
+ }
+
+ metrics.Add(new XElement(H.RunCount, new XAttribute(H.Val, formattingMetrics.RunCount)));
+ if (formattingMetrics.RunWithoutRprCount > 0)
+ metrics.Add(new XElement(H.RunWithoutRprCount, new XAttribute(H.Val, formattingMetrics.RunWithoutRprCount)));
+ if (formattingMetrics.ZeroLengthText > 0)
+ metrics.Add(new XElement(H.ZeroLengthText, new XAttribute(H.Val, formattingMetrics.ZeroLengthText)));
+ if (formattingMetrics.MultiFontRun > 0)
+ metrics.Add(new XElement(H.MultiFontRun, new XAttribute(H.Val, formattingMetrics.MultiFontRun)));
+ if (formattingMetrics.AsciiCharCount > 0)
+ metrics.Add(new XElement(H.AsciiCharCount, new XAttribute(H.Val, formattingMetrics.AsciiCharCount)));
+ if (formattingMetrics.CSCharCount > 0)
+ metrics.Add(new XElement(H.CSCharCount, new XAttribute(H.Val, formattingMetrics.CSCharCount)));
+ if (formattingMetrics.EastAsiaCharCount > 0)
+ metrics.Add(new XElement(H.EastAsiaCharCount, new XAttribute(H.Val, formattingMetrics.EastAsiaCharCount)));
+ if (formattingMetrics.HAnsiCharCount > 0)
+ metrics.Add(new XElement(H.HAnsiCharCount, new XAttribute(H.Val, formattingMetrics.HAnsiCharCount)));
+ if (formattingMetrics.AsciiRunCount > 0)
+ metrics.Add(new XElement(H.AsciiRunCount, new XAttribute(H.Val, formattingMetrics.AsciiRunCount)));
+ if (formattingMetrics.CSRunCount > 0)
+ metrics.Add(new XElement(H.CSRunCount, new XAttribute(H.Val, formattingMetrics.CSRunCount)));
+ if (formattingMetrics.EastAsiaRunCount > 0)
+ metrics.Add(new XElement(H.EastAsiaRunCount, new XAttribute(H.Val, formattingMetrics.EastAsiaRunCount)));
+ if (formattingMetrics.HAnsiRunCount > 0)
+ metrics.Add(new XElement(H.HAnsiRunCount, new XAttribute(H.Val, formattingMetrics.HAnsiRunCount)));
+
+ if (formattingMetrics.Languages.Any())
+ {
+ var uls = formattingMetrics.Languages.StringConcatenate(s => s + ",").TrimEnd(',');
+ metrics.Add(new XElement(H.Languages, new XAttribute(H.Val, PtUtils.MakeValidXml(uls))));
+ }
+ }
+
+ private static void AnalyzeRun(XElement run, List<XElement> attList, List<string> notes, FormattingMetrics formattingMetrics, string uri)
+ {
+ var runText = run.Elements()
+ .Where(e => e.Name == W.t || e.Name == W.delText)
+ .Select(t => (string)t)
+ .StringConcatenate();
+ if (runText.Length == 0)
+ {
+ formattingMetrics.ZeroLengthText++;
+ return;
+ }
+ var rPr = run.Element(W.rPr);
+ if (rPr == null)
+ {
+ formattingMetrics.RunWithoutRprCount++;
+ notes.Add(PtUtils.MakeValidXml(string.Format("Error in part {0}: run without rPr at {1}", uri, run.GetXPath())));
+ rPr = new XElement(W.rPr);
+ }
+ FormattingAssembler.CharStyleAttributes csa = new FormattingAssembler.CharStyleAttributes(null, rPr);
+ var fontTypeArray = runText
+ .Select(ch => FormattingAssembler.DetermineFontTypeFromCharacter(ch, csa))
+ .ToArray();
+ var distinctFontTypeArray = fontTypeArray
+ .Distinct()
+ .ToArray();
+ var distinctFonts = distinctFontTypeArray
+ .Select(ft =>
+ {
+ return GetFontFromFontType(csa, ft);
+ })
+ .Distinct();
+ var languages = distinctFontTypeArray
+ .Select(ft =>
+ {
+ if (ft == FormattingAssembler.FontType.Ascii)
+ return csa.LatinLang;
+ if (ft == FormattingAssembler.FontType.CS)
+ return csa.BidiLang;
+ if (ft == FormattingAssembler.FontType.EastAsia)
+ return csa.EastAsiaLang;
+ //if (ft == FormattingAssembler.FontType.HAnsi)
+ return csa.LatinLang;
+ })
+ .Select(l =>
+ {
+ if (l == "" || l == null)
+ return /* "Dflt:" + */ CultureInfo.CurrentCulture.Name;
+ return l;
+ })
+ //.Where(l => l != null && l != "")
+ .Distinct();
+ if (languages.Any(l => !formattingMetrics.Languages.Contains(l)))
+ formattingMetrics.Languages = formattingMetrics.Languages.Concat(languages).Distinct().ToList();
+ var multiFontRun = distinctFonts.Count() > 1;
+ if (multiFontRun)
+ {
+ formattingMetrics.MultiFontRun++;
+
+ formattingMetrics.AsciiCharCount += fontTypeArray.Where(ft => ft == FormattingAssembler.FontType.Ascii).Count();
+ formattingMetrics.CSCharCount += fontTypeArray.Where(ft => ft == FormattingAssembler.FontType.CS).Count();
+ formattingMetrics.EastAsiaCharCount += fontTypeArray.Where(ft => ft == FormattingAssembler.FontType.EastAsia).Count();
+ formattingMetrics.HAnsiCharCount += fontTypeArray.Where(ft => ft == FormattingAssembler.FontType.HAnsi).Count();
+ }
+ else
+ {
+ switch (fontTypeArray[0])
+ {
+ case FormattingAssembler.FontType.Ascii:
+ formattingMetrics.AsciiCharCount += runText.Length;
+ formattingMetrics.AsciiRunCount++;
+ break;
+ case FormattingAssembler.FontType.CS:
+ formattingMetrics.CSCharCount += runText.Length;
+ formattingMetrics.CSRunCount++;
+ break;
+ case FormattingAssembler.FontType.EastAsia:
+ formattingMetrics.EastAsiaCharCount += runText.Length;
+ formattingMetrics.EastAsiaRunCount++;
+ break;
+ case FormattingAssembler.FontType.HAnsi:
+ formattingMetrics.HAnsiCharCount += runText.Length;
+ formattingMetrics.HAnsiRunCount++;
+ break;
+ }
+ }
+ }
+
+ private static string GetFontFromFontType(FormattingAssembler.CharStyleAttributes csa, FormattingAssembler.FontType ft)
+ {
+ switch (ft)
+ {
+ case FormattingAssembler.FontType.Ascii:
+ return csa.AsciiFont;
+ case FormattingAssembler.FontType.CS:
+ return csa.CsFont;
+ case FormattingAssembler.FontType.EastAsia:
+ return csa.EastAsiaFont;
+ case FormattingAssembler.FontType.HAnsi:
+ return csa.HAnsiFont;
+ default: // dummy
+ return csa.AsciiFont;
+ }
+ }
+
+ public static XElement GetXlsxMetrics(SmlDocument smlDoc, MetricsGetterSettings settings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(smlDoc))
+ {
+ using (SpreadsheetDocument sDoc = streamDoc.GetSpreadsheetDocument())
+ {
+ List<XElement> metrics = new List<XElement>();
+
+ bool valid = ValidateAgainstSpecificVersion(sDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2007, H.SdkValidationError2007);
+ valid |= ValidateAgainstSpecificVersion(sDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2010, H.SdkValidationError2010);
+#if !NET35
+ valid |= ValidateAgainstSpecificVersion(sDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2013, H.SdkValidationError2013);
+#endif
+
+ return new XElement(H.Metrics,
+ new XAttribute(H.FileName, smlDoc.FileName),
+ new XAttribute(H.FileType, "SpreadsheetML"),
+ metrics,
+ GetTableInfoForWorkbook(sDoc, settings),
+ settings.RetrieveNamespaceList ? RetrieveNamespaceList(sDoc) : null,
+ settings.RetrieveContentTypeList ? RetrieveContentTypeList(sDoc) : null);
+ }
+ }
+ }
+
+ private static XElement GetTableInfoForWorkbook(SpreadsheetDocument spreadsheet, MetricsGetterSettings settings)
+ {
+ var workbookPart = spreadsheet.WorkbookPart;
+ var xd = workbookPart.GetXDocument();
+ var partInformation =
+ new XElement(H.Sheets,
+ xd.Root
+ .Element(S.sheets)
+ .Elements(S.sheet)
+ .Select(sh =>
+ {
+ var rid = (string)sh.Attribute(R.id);
+ var sheetName = (string)sh.Attribute("name");
+ WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(rid);
+ return GetTableInfoForSheet(spreadsheet, worksheetPart, sheetName, settings);
+ }));
+ return partInformation;
+ }
+
+ public static XElement GetTableInfoForSheet(SpreadsheetDocument spreadsheetDocument, WorksheetPart sheetPart, string sheetName,
+ MetricsGetterSettings settings)
+ {
+ var xd = sheetPart.GetXDocument();
+ XElement sheetInformation = new XElement(H.Sheet,
+ new XAttribute(H.Name, sheetName),
+ xd.Root.Elements(S.tableParts).Elements(S.tablePart).Select(tp =>
+ {
+ string rId = (string)tp.Attribute(R.id);
+ TableDefinitionPart tablePart = (TableDefinitionPart)sheetPart.GetPartById(rId);
+ var txd = tablePart.GetXDocument();
+ var tableName = (string)txd.Root.Attribute("displayName");
+ XElement tableCellData = null;
+ if (settings.IncludeXlsxTableCellData)
+ {
+ var xlsxTable = spreadsheetDocument.Table(tableName);
+ tableCellData = new XElement(H.TableData,
+ xlsxTable.TableRows()
+ .Select(row =>
+ {
+ var rowElement = new XElement(H.Row,
+ xlsxTable.TableColumns().Select(col =>
+ {
+ var cellElement = new XElement(H.Cell,
+ new XAttribute(H.Name, col.Name),
+ new XAttribute(H.Val, (string)row[col.Name]));
+ return cellElement;
+ }));
+ return rowElement;
+ }));
+ }
+ var table = new XElement(H.Table,
+ new XAttribute(H.Name, (string)txd.Root.Attribute("name")),
+ new XAttribute(H.DisplayName, tableName),
+ new XElement(H.Columns,
+ txd.Root.Element(S.tableColumns).Elements(S.tableColumn)
+ .Select(tc => new XElement(H.Column,
+ new XAttribute(H.Name, (string)tc.Attribute("name"))))),
+ tableCellData
+ );
+ return table;
+ })
+ );
+ if (!sheetInformation.HasElements)
+ return null;
+ return sheetInformation;
+ }
+
+ public static XElement GetPptxMetrics(PmlDocument pmlDoc, MetricsGetterSettings settings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(pmlDoc))
+ {
+ using (PresentationDocument pDoc = streamDoc.GetPresentationDocument())
+ {
+ List<XElement> metrics = new List<XElement>();
+
+ bool valid = ValidateAgainstSpecificVersion(pDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2007, H.SdkValidationError2007);
+ valid |= ValidateAgainstSpecificVersion(pDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2010, H.SdkValidationError2010);
+#if !NET35
+ valid |= ValidateAgainstSpecificVersion(pDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2013, H.SdkValidationError2013);
+#endif
+ return new XElement(H.Metrics,
+ new XAttribute(H.FileName, pmlDoc.FileName),
+ new XAttribute(H.FileType, "PresentationML"),
+ metrics,
+ settings.RetrieveNamespaceList ? RetrieveNamespaceList(pDoc) : null,
+ settings.RetrieveContentTypeList ? RetrieveContentTypeList(pDoc) : null);
+ }
+ }
+ }
+
+ private static object GetStyleHierarchy(WordprocessingDocument document)
+ {
+ var stylePart = document.MainDocumentPart.StyleDefinitionsPart;
+ if (stylePart == null)
+ return null;
+ var xd = stylePart.GetXDocument();
+ var stylesWithPath = xd.Root
+ .Elements(W.style)
+ .Select(s =>
+ {
+ var styleString = (string)s.Attribute(W.styleId);
+ var thisStyle = s;
+ while (true)
+ {
+ var baseStyle = (string)thisStyle.Elements(W.basedOn).Attributes(W.val).FirstOrDefault();
+ if (baseStyle == null)
+ break;
+ styleString = baseStyle + "/" + styleString;
+ thisStyle = xd.Root.Elements(W.style).FirstOrDefault(ts => ts.Attribute(W.styleId).Value == baseStyle);
+ if (thisStyle == null)
+ break;
+ }
+ return styleString;
+ })
+ .OrderBy(n => n)
+ .ToList();
+ XElement styleHierarchy = new XElement(H.StyleHierarchy);
+ foreach (var item in stylesWithPath)
+ {
+ var styleChain = item.Split('/');
+ XElement elementToAddTo = styleHierarchy;
+ foreach (var inChain in styleChain.SkipLast(1))
+ elementToAddTo = elementToAddTo.Elements(H.Style).FirstOrDefault(z => z.Attribute(H.Id).Value == inChain);
+ var styleToAdd = styleChain.Last();
+ elementToAddTo.Add(
+ new XElement(H.Style,
+ new XAttribute(H.Id, styleChain.Last()),
+ new XAttribute(H.Type, (string)xd.Root.Elements(W.style).First(z => z.Attribute(W.styleId).Value == styleToAdd).Attribute(W.type))));
+ }
+ return styleHierarchy;
+ }
+
+ private static XElement GetMetricsForWmlPart(OpenXmlPart part, MetricsGetterSettings settings)
+ {
+ XElement contentControls = null;
+ if (part is MainDocumentPart ||
+ part is HeaderPart ||
+ part is FooterPart ||
+ part is FootnotesPart ||
+ part is EndnotesPart)
+ {
+ var xd = part.GetXDocument();
+ contentControls = (XElement)GetContentControlsTransform(xd.Root, settings);
+ if (!contentControls.HasElements)
+ contentControls = null;
+ }
+ var partMetrics = new XElement(H.Part,
+ new XAttribute(H.ContentType, part.ContentType),
+ new XAttribute(H.Uri, part.Uri.ToString()),
+ contentControls);
+ if (partMetrics.HasElements)
+ return partMetrics;
+ return null;
+ }
+
+ private static object GetContentControlsTransform(XNode node, MetricsGetterSettings settings)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element == element.Document.Root)
+ return new XElement(H.ContentControls,
+ element.Nodes().Select(n => GetContentControlsTransform(n, settings)));
+
+ if (element.Name == W.sdt)
+ {
+ var tag = (string)element.Elements(W.sdtPr).Elements(W.tag).Attributes(W.val).FirstOrDefault();
+ XAttribute tagAttr = tag != null ? new XAttribute(H.Tag, tag) : null;
+
+ var alias = (string)element.Elements(W.sdtPr).Elements(W.alias).Attributes(W.val).FirstOrDefault();
+ XAttribute aliasAttr = alias != null ? new XAttribute(H.Alias, alias) : null;
+
+ var xPathAttr = new XAttribute(H.XPath, element.GetXPath());
+
+ var isText = element.Elements(W.sdtPr).Elements(W.text).Any();
+ var isBibliography = element.Elements(W.sdtPr).Elements(W.bibliography).Any();
+ var isCitation = element.Elements(W.sdtPr).Elements(W.citation).Any();
+ var isComboBox = element.Elements(W.sdtPr).Elements(W.comboBox).Any();
+ var isDate = element.Elements(W.sdtPr).Elements(W.date).Any();
+ var isDocPartList = element.Elements(W.sdtPr).Elements(W.docPartList).Any();
+ var isDocPartObj = element.Elements(W.sdtPr).Elements(W.docPartObj).Any();
+ var isDropDownList = element.Elements(W.sdtPr).Elements(W.dropDownList).Any();
+ var isEquation = element.Elements(W.sdtPr).Elements(W.equation).Any();
+ var isGroup = element.Elements(W.sdtPr).Elements(W.group).Any();
+ var isPicture = element.Elements(W.sdtPr).Elements(W.picture).Any();
+ var isRichText = element.Elements(W.sdtPr).Elements(W.richText).Any() ||
+ (! isText &&
+ ! isBibliography &&
+ ! isCitation &&
+ ! isComboBox &&
+ ! isDate &&
+ ! isDocPartList &&
+ ! isDocPartObj &&
+ ! isDropDownList &&
+ ! isEquation &&
+ ! isGroup &&
+ ! isPicture);
+ string type = null;
+ if (isText ) type = "Text";
+ if (isBibliography) type = "Bibliography";
+ if (isCitation ) type = "Citation";
+ if (isComboBox ) type = "ComboBox";
+ if (isDate ) type = "Date";
+ if (isDocPartList ) type = "DocPartList";
+ if (isDocPartObj ) type = "DocPartObj";
+ if (isDropDownList) type = "DropDownList";
+ if (isEquation ) type = "Equation";
+ if (isGroup ) type = "Group";
+ if (isPicture ) type = "Picture";
+ if (isRichText ) type = "RichText";
+ var typeAttr = new XAttribute(H.Type, type);
+
+ return new XElement(H.ContentControl,
+ typeAttr,
+ tagAttr,
+ aliasAttr,
+ xPathAttr,
+ element.Nodes().Select(n => GetContentControlsTransform(n, settings)));
+ }
+
+ return element.Nodes().Select(n => GetContentControlsTransform(n, settings));
+ }
+ if (settings.IncludeTextInContentControls)
+ return node;
+ return null;
+ }
+ }
+
+ public static class H
+ {
+ public static XName ActiveX = "ActiveX";
+ public static XName Alias = "Alias";
+ public static XName AltChunk = "AltChunk";
+ public static XName Arguments = "Arguments";
+ public static XName AsciiCharCount = "AsciiCharCount";
+ public static XName AsciiRunCount = "AsciiRunCount";
+ public static XName AverageParagraphLength = "AverageParagraphLength";
+ public static XName BaselineReport = "BaselineReport";
+ public static XName Batch = "Batch";
+ public static XName BatchName = "BatchName";
+ public static XName BatchSelector = "BatchSelector";
+ public static XName CSCharCount = "CSCharCount";
+ public static XName CSRunCount = "CSRunCount";
+ public static XName Catalog = "Catalog";
+ public static XName CatalogList = "CatalogList";
+ public static XName CatalogListFile = "CatalogListFile";
+ public static XName CaughtException = "CaughtException";
+ public static XName Cell = "Cell";
+ public static XName Column = "Column";
+ public static XName Columns = "Columns";
+ public static XName ComplexField = "ComplexField";
+ public static XName Computer = "Computer";
+ public static XName Computers = "Computers";
+ public static XName ContentControl = "ContentControl";
+ public static XName ContentControls = "ContentControls";
+ public static XName ContentType = "ContentType";
+ public static XName ContentTypes = "ContentTypes";
+ public static XName CustomXmlMarkup = "CustomXmlMarkup";
+ public static XName DLL = "DLL";
+ public static XName DefaultDialogValuesFile = "DefaultDialogValuesFile";
+ public static XName DefaultValues = "DefaultValues";
+ public static XName Dependencies = "Dependencies";
+ public static XName DestinationDir = "DestinationDir";
+ public static XName Directory = "Directory";
+ public static XName DirectoryPattern = "DirectoryPattern";
+ public static XName DisplayName = "DisplayName";
+ public static XName DoJobQueueName = "DoJobQueueName";
+ public static XName Document = "Document";
+ public static XName DocumentProtection = "DocumentProtection";
+ public static XName DocumentSelector = "DocumentSelector";
+ public static XName DocumentType = "DocumentType";
+ public static XName Documents = "Documents";
+ public static XName EastAsiaCharCount = "EastAsiaCharCount";
+ public static XName EastAsiaRunCount = "EastAsiaRunCount";
+ public static XName ElementCount = "ElementCount";
+ public static XName EmbeddedXlsx = "EmbeddedXlsx";
+ public static XName Error = "Error";
+ public static XName Exception = "Exception";
+ public static XName Exe = "Exe";
+ public static XName ExeRoot = "ExeRoot";
+ public static XName Extension = "Extension";
+ public static XName File = "File";
+ public static XName FileLength = "FileLength";
+ public static XName FileName = "FileName";
+ public static XName FilePattern = "FilePattern";
+ public static XName FileType = "FileType";
+ public static XName Guid = "Guid";
+ public static XName HAnsiCharCount = "HAnsiCharCount";
+ public static XName HAnsiRunCount = "HAnsiRunCount";
+ public static XName RevisionTracking = "RevisionTracking";
+ public static XName Hyperlink = "Hyperlink";
+ public static XName IPAddress = "IPAddress";
+ public static XName Id = "Id";
+ public static XName Invalid = "Invalid";
+ public static XName InvalidHyperlink = "InvalidHyperlink";
+ public static XName InvalidHyperlinkException = "InvalidHyperlinkException";
+ public static XName InvalidSaveThroughXslt = "InvalidSaveThroughXslt";
+ public static XName JobComplete = "JobComplete";
+ public static XName JobExe = "JobExe";
+ public static XName JobName = "JobName";
+ public static XName JobSpec = "JobSpec";
+ public static XName Languages = "Languages";
+ public static XName LegacyFrame = "LegacyFrame";
+ public static XName LocalDoJobQueue = "LocalDoJobQueue";
+ public static XName MachineName = "MachineName";
+ public static XName MaxConcurrentJobs = "MaxConcurrentJobs";
+ public static XName MaxDocumentsInJob = "MaxDocumentsInJob";
+ public static XName MaxParagraphLength = "MaxParagraphLength";
+ public static XName Message = "Message";
+ public static XName Metrics = "Metrics";
+ public static XName MultiDirectory = "MultiDirectory";
+ public static XName MultiFontRun = "MultiFontRun";
+ public static XName MultiServerQueue = "MultiServerQueue";
+ public static XName Name = "Name";
+ public static XName Namespaces = "Namespaces";
+ public static XName Namespace = "Namespace";
+ public static XName NamespaceName = "NamespaceName";
+ public static XName NamespacePrefix = "NamespacePrefix";
+ public static XName Note = "Note";
+ public static XName NumberingFormatList = "NumberingFormatList";
+ public static XName ObjectDisposedException = "ObjectDisposedException";
+ public static XName ParagraphCount = "ParagraphCount";
+ public static XName Part = "Part";
+ public static XName Parts = "Parts";
+ public static XName PassedDocuments = "PassedDocuments";
+ public static XName Path = "Path";
+ public static XName ProduceCatalog = "ProduceCatalog";
+ public static XName ReferenceToNullImage = "ReferenceToNullImage";
+ public static XName Report = "Report";
+ public static XName Root = "Root";
+ public static XName RootDirectory = "RootDirectory";
+ public static XName Row = "Row";
+ public static XName RunCount = "RunCount";
+ public static XName RunWithoutRprCount = "RunWithoutRprCount";
+ public static XName SdkValidationError = "SdkValidationError";
+ public static XName SdkValidationError2007 = "SdkValidationError2007";
+ public static XName SdkValidationError2010 = "SdkValidationError2010";
+ public static XName SdkValidationError2013 = "SdkValidationError2013";
+ public static XName Sheet = "Sheet";
+ public static XName Sheets = "Sheets";
+ public static XName SimpleField = "SimpleField";
+ public static XName Skip = "Skip";
+ public static XName SmartTag = "SmartTag";
+ public static XName SourceRootDir = "SourceRootDir";
+ public static XName SpawnerJobExeLocation = "SpawnerJobExeLocation";
+ public static XName SpawnerReady = "SpawnerReady";
+ public static XName Style = "Style";
+ public static XName StyleHierarchy = "StyleHierarchy";
+ public static XName SubDocument = "SubDocument";
+ public static XName Table = "Table";
+ public static XName TableData = "TableData";
+ public static XName Tag = "Tag";
+ public static XName Take = "Take";
+ public static XName TextBox = "TextBox";
+ public static XName TrackRevisionsEnabled = "TrackRevisionsEnabled";
+ public static XName Type = "Type";
+ public static XName Uri = "Uri";
+ public static XName Val = "Val";
+ public static XName Valid = "Valid";
+ public static XName WindowStyle = "WindowStyle";
+ public static XName XPath = "XPath";
+ public static XName ZeroLengthText = "ZeroLengthText";
+ public static XName custDataLst = "custDataLst";
+ public static XName custShowLst = "custShowLst";
+ public static XName kinsoku = "kinsoku";
+ public static XName modifyVerifier = "modifyVerifier";
+ public static XName photoAlbum = "photoAlbum";
+ }
+}
diff --git a/OpenXmlPowerTools/OpenXmlPowerTools.csproj b/OpenXmlPowerTools/OpenXmlPowerTools.csproj
new file mode 100644
index 0000000..a8a7684
--- /dev/null
+++ b/OpenXmlPowerTools/OpenXmlPowerTools.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworks>net45;net46</TargetFrameworks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup Condition=" '$(TargetFramework)' == 'net45'">
+ <Reference Include="System.IO.Compression" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+
+ <ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
+ <Reference Include="System.IO.Compression" />
+ <Reference Include="System.Windows.Forms" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerTools/OpenXmlPowerTools.csproj.DotSettings b/OpenXmlPowerTools/OpenXmlPowerTools.csproj.DotSettings
new file mode 100644
index 0000000..848f121
--- /dev/null
+++ b/OpenXmlPowerTools/OpenXmlPowerTools.csproj.DotSettings
@@ -0,0 +1,3 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp40</s:String>
+</wpf:ResourceDictionary>
diff --git a/OpenXmlPowerTools/OpenXmlRegex.cs b/OpenXmlPowerTools/OpenXmlRegex.cs
new file mode 100644
index 0000000..1ad0124
--- /dev/null
+++ b/OpenXmlPowerTools/OpenXmlRegex.cs
@@ -0,0 +1,577 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+
+namespace OpenXmlPowerTools
+{
+ public class OpenXmlRegex
+ {
+ private const string DontConsolidate = "DontConsolidate";
+
+ private static readonly HashSet<XName> RevTrackMarkupWithId = new HashSet<XName>
+ {
+ W.cellDel,
+ W.cellIns,
+ W.cellMerge,
+ W.customXmlDelRangeEnd,
+ W.customXmlDelRangeStart,
+ W.customXmlInsRangeEnd,
+ W.customXmlInsRangeStart,
+ W.customXmlMoveFromRangeEnd,
+ W.customXmlMoveFromRangeStart,
+ W.customXmlMoveToRangeEnd,
+ W.customXmlMoveToRangeStart,
+ W.del,
+ W.ins,
+ W.moveFrom,
+ W.moveFromRangeEnd,
+ W.moveFromRangeStart,
+ W.moveTo,
+ W.moveToRangeEnd,
+ W.moveToRangeStart,
+ W.pPrChange,
+ W.rPrChange,
+ W.sectPrChange,
+ W.tblGridChange,
+ W.tblPrChange,
+ W.tblPrExChange,
+ W.tcPrChange
+ };
+
+ public static int Match(IEnumerable<XElement> content, Regex regex)
+ {
+ return ReplaceInternal(content, regex, null, null, false, null, true);
+ }
+
+ /// <summary>
+ /// If callback == null Then returns count of matches in the content
+ /// If callback != null Then Match calls Found for each match
+ /// </summary>
+ public static int Match(IEnumerable<XElement> content, Regex regex, Action<XElement, Match> found)
+ {
+ return ReplaceInternal(content, regex, null,
+ (x, m) =>
+ {
+ if (found != null) found.Invoke(x, m);
+ return true;
+ },
+ false, null, true);
+ }
+
+ /// <summary>
+ /// If replacement == "new content" && callback == null
+ /// Then replaces all matches
+ /// If replacement == "" && callback == null)
+ /// Then deletes all matches
+ /// If replacement == "new content" && callback != null)
+ /// Then the callback can return true / false to indicate whether to replace or not
+ /// If the callback returns true once, and false on all subsequent calls, then this method replaces only the first found.
+ /// If replacement == "" && callback != null)
+ /// Then the callback can return true / false to indicate whether to delete or not
+ /// </summary>
+ public static int Replace(IEnumerable<XElement> content, Regex regex, string replacement,
+ Func<XElement, Match, bool> doReplacement)
+ {
+ return ReplaceInternal(content, regex, replacement, doReplacement, false, null, true);
+ }
+
+ /// <summary>
+ /// This overload enables not coalescing content, which is necessary for DocumentAssembler.
+ /// </summary>
+ public static int Replace(IEnumerable<XElement> content, Regex regex, string replacement,
+ Func<XElement, Match, bool> doReplacement, bool coalesceContent)
+ {
+ return ReplaceInternal(content, regex, replacement, doReplacement, false, null, coalesceContent);
+ }
+
+ /// <summary>
+ /// If replacement == "new content" && callback == null
+ /// Then replaces all matches
+ /// If replacement == "" && callback == null)
+ /// Then deletes all matches
+ /// If replacement == "new content" && callback != null)
+ /// Then the callback can return true / false to indicate whether to replace or not
+ /// If the callback returns true once, and false on all subsequent calls, then this method replaces only the first found.
+ /// If replacement == "" && callback != null)
+ /// Then the callback can return true / false to indicate whether to delete or not
+ /// If trackRevisions == true
+ /// Then replacement is done using revision tracking markup, with author as the revision tracking author
+ /// If trackRevisions == true for a PPTX
+ /// Then code throws an exception
+ /// </summary>
+ public static int Replace(IEnumerable<XElement> content, Regex regex, string replacement,
+ Func<XElement, Match, bool> doReplacement, bool trackRevisions, string author)
+ {
+ return ReplaceInternal(content, regex, replacement, doReplacement, trackRevisions, author, true);
+ }
+
+ private static int ReplaceInternal(IEnumerable<XElement> content, Regex regex, string replacement,
+ Func<XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor,
+ bool coalesceContent)
+ {
+ if (content == null) throw new ArgumentNullException("content");
+ if (regex == null) throw new ArgumentNullException("regex");
+
+ IEnumerable<XElement> contentList = content as IList<XElement> ?? content.ToList();
+
+ XElement first = contentList.FirstOrDefault();
+ if (first == null)
+ return 0;
+
+ if (first.Name.Namespace == W.w)
+ {
+ if (!contentList.Any())
+ return 0;
+
+ var replInfo = new ReplaceInternalInfo { Count = 0 };
+ foreach (XElement c in contentList)
+ {
+ var newC = (XElement) WmlSearchAndReplaceTransform(c, regex, replacement, callback, trackRevisions,
+ revisionTrackingAuthor, replInfo, coalesceContent);
+ c.ReplaceNodes(newC.Nodes());
+ }
+
+ XElement root = contentList.First().AncestorsAndSelf().Last();
+ int nextId = new[] { 0 }
+ .Concat(root
+ .Descendants()
+ .Where(d => RevTrackMarkupWithId.Contains(d.Name))
+ .Attributes(W.id)
+ .Select(a => (int) a))
+ .Max() + 1;
+ IEnumerable<XElement> revTrackingWithoutId = root
+ .DescendantsAndSelf()
+ .Where(d => RevTrackMarkupWithId.Contains(d.Name) && (d.Attribute(W.id) == null));
+ foreach (XElement item in revTrackingWithoutId)
+ item.Add(new XAttribute(W.id, nextId++));
+
+ List<IGrouping<int, XElement>> revTrackingWithDuplicateIds = root
+ .DescendantsAndSelf()
+ .Where(d => RevTrackMarkupWithId.Contains(d.Name))
+ .GroupBy(d => (int) d.Attribute(W.id))
+ .Where(g => g.Count() > 1)
+ .ToList();
+ foreach (IGrouping<int, XElement> group in revTrackingWithDuplicateIds)
+ foreach (XElement gc in group.Skip(1))
+ {
+ XAttribute xAttribute = gc.Attribute(W.id);
+ if (xAttribute != null) xAttribute.Value = nextId.ToString();
+ nextId++;
+ }
+
+ return replInfo.Count;
+ }
+
+ if ((first.Name.Namespace == P.p) || (first.Name.Namespace == A.a))
+ {
+ if (trackRevisions)
+ throw new OpenXmlPowerToolsException("PPTX does not support revision tracking");
+
+ var counter = new ReplaceInternalInfo { Count = 0 };
+ foreach (XElement c in contentList)
+ {
+ var newC = (XElement) PmlSearchAndReplaceTransform(c, regex, replacement, callback, counter);
+ c.ReplaceNodes(newC.Nodes());
+ }
+
+ return counter.Count;
+ }
+
+ return 0;
+ }
+
+ private static object WmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement,
+ Func<XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor,
+ ReplaceInternalInfo replInfo, bool coalesceContent)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == W.p)
+ {
+ XElement paragraph = element;
+
+ string preliminaryContent = paragraph
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.r && (d.Parent == null || d.Parent.Name != W.del))
+ .Select(UnicodeMapper.RunToString)
+ .StringConcatenate();
+ if (regex.IsMatch(preliminaryContent))
+ {
+ var paragraphWithSplitRuns = new XElement(W.p,
+ paragraph.Attributes(),
+ paragraph.Nodes().Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback,
+ trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent)));
+
+ IEnumerable<XElement> runsTrimmed = paragraphWithSplitRuns
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.r && (d.Parent == null || d.Parent.Name != W.del));
+
+ var charsAndRuns = runsTrimmed
+ .Select(r => new { Ch = UnicodeMapper.RunToString(r), r })
+ .ToList();
+
+ string content = charsAndRuns.Select(t => t.Ch).StringConcatenate();
+ XElement[] alignedRuns = charsAndRuns.Select(t => t.r).ToArray();
+
+ MatchCollection matchCollection = regex.Matches(content);
+ replInfo.Count += matchCollection.Count;
+
+ // Process Match
+ if (replacement == null)
+ {
+ if (callback == null) return paragraph;
+
+ foreach (Match match in matchCollection.Cast<Match>())
+ callback(paragraph, match);
+
+ return paragraph;
+ }
+
+ // Process Replace
+ foreach (Match match in matchCollection.Cast<Match>())
+ {
+ if (match.Length == 0) continue;
+ if ((callback != null) && !callback(paragraph, match)) continue;
+
+ List<XElement> runCollection = alignedRuns
+ .Skip(match.Index)
+ .Take(match.Length)
+ .ToList();
+
+ // uses the Skip / Take special semantics of array to implement efficient finding of sub array
+
+ XElement firstRun = runCollection.First();
+ XElement firstRunProperties = firstRun.Elements(W.rPr).FirstOrDefault();
+
+ // save away first run properties
+
+ if (trackRevisions)
+ {
+ if (replacement != "")
+ {
+ // We coalesce runs as some methods, e.g., in DocumentAssembler,
+ // will try to find the replacement string even though they
+ // set coalesceContent to false.
+ string newTextValue = match.Result(replacement);
+ List<XElement> newRuns = UnicodeMapper.StringToCoalescedRunList(newTextValue,
+ firstRunProperties);
+ var newIns = new XElement(W.ins,
+ new XAttribute(W.author, revisionTrackingAuthor),
+ new XAttribute(W.date, DateTime.UtcNow.ToString("s") + "Z"),
+ newRuns);
+
+ if (firstRun.Parent != null && firstRun.Parent.Name == W.ins)
+ firstRun.Parent.AddBeforeSelf(newIns);
+ else
+ firstRun.AddBeforeSelf(newIns);
+ }
+
+ foreach (XElement run in runCollection)
+ {
+ bool isInIns = run.Parent != null && run.Parent.Name == W.ins;
+ if (isInIns)
+ {
+ XElement parentIns = run.Parent;
+ XElement grandParentParagraph = parentIns.Parent;
+ if (grandParentParagraph != null)
+ {
+ if ((string) parentIns.Attributes(W.author).FirstOrDefault() ==
+ revisionTrackingAuthor)
+ {
+ List<XElement> parentInsSiblings = grandParentParagraph
+ .Elements()
+ .Where(c => c != parentIns)
+ .ToList();
+ grandParentParagraph.ReplaceNodes(parentInsSiblings);
+ }
+ else
+ {
+ List<XElement> parentInsSiblings = grandParentParagraph
+ .Elements()
+ .Select(c => c == parentIns
+ ? new XElement(W.ins,
+ parentIns.Attributes(),
+ new XElement(W.del,
+ new XAttribute(W.author, revisionTrackingAuthor),
+ new XAttribute(W.date, DateTime.UtcNow.ToString("s") + "Z"),
+ parentIns.Elements().Select(TransformToDelText)))
+ : c)
+ .ToList();
+ grandParentParagraph.ReplaceNodes(parentInsSiblings);
+ }
+ }
+ }
+ else
+ {
+ var delRun = new XElement(W.del,
+ new XAttribute(W.author, revisionTrackingAuthor),
+ new XAttribute(W.date, DateTime.UtcNow.ToString("s") + "Z"),
+ TransformToDelText(run));
+ run.ReplaceWith(delRun);
+ }
+ }
+ }
+ else // not tracked revisions
+ {
+ foreach (XElement runToDelete in runCollection.Skip(1).ToList())
+ if (runToDelete.Parent != null && runToDelete.Parent.Name == W.ins)
+ runToDelete.Parent.Remove();
+ else
+ runToDelete.Remove();
+
+ // We coalesce runs as some methods, e.g., in DocumentAssembler,
+ // will try to find the replacement string even though they
+ // set coalesceContent to false.
+ string newTextValue = match.Result(replacement);
+ List<XElement> newRuns = UnicodeMapper.StringToCoalescedRunList(newTextValue,
+ firstRunProperties);
+ if (firstRun.Parent != null && firstRun.Parent.Name == W.ins)
+ firstRun.Parent.ReplaceWith(newRuns);
+ else
+ firstRun.ReplaceWith(newRuns);
+ }
+ }
+
+ return coalesceContent
+ ? WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(paragraphWithSplitRuns)
+ : paragraphWithSplitRuns;
+ }
+
+ var newParagraph = new XElement(W.p,
+ paragraph.Attributes(),
+ paragraph.Nodes().Select(n =>
+ {
+ var e = n as XElement;
+ if (e == null) return n;
+
+ if (e.Name == W.pPr)
+ return e;
+ if (((e.Name == W.r) && e.Elements(W.t).Any()) || e.Elements(W.tab).Any())
+ return e;
+ if ((e.Name == W.ins) && e.Elements(W.r).Elements(W.t).Any())
+ return e;
+
+ return WmlSearchAndReplaceTransform(e, regex, replacement, callback,
+ trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent);
+ }));
+ return coalesceContent
+ ? WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(newParagraph) // CoalesceContent(newParagraph)
+ : newParagraph;
+ }
+
+ if (element.Name == W.ins && element.Elements(W.r).Any())
+ {
+ List<object> collectionOfCollections = element
+ .Elements()
+ .Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions,
+ revisionTrackingAuthor, replInfo, coalesceContent))
+ .ToList();
+ List<object> collectionOfIns = collectionOfCollections
+ .Select(c =>
+ {
+ var elements = c as IEnumerable<XElement>;
+ return elements != null
+ ? elements.Select(ixc => new XElement(W.ins, element.Attributes(), ixc))
+ : c;
+ })
+ .ToList();
+ return collectionOfIns;
+ }
+
+ if (element.Name == W.r)
+ {
+ return element.Elements()
+ .Where(e => e.Name != W.rPr)
+ .Select(e => e.Name == W.t
+ ? ((string) e).Select(c =>
+ new XElement(W.r,
+ element.Elements(W.rPr),
+ new XElement(W.t, XmlUtil.GetXmlSpaceAttribute(c), c)))
+ : new[] { new XElement(W.r, element.Elements(W.rPr), e) })
+ .SelectMany(t => t);
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes()
+ .Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions,
+ revisionTrackingAuthor, replInfo, coalesceContent)));
+ }
+
+ private static object TransformToDelText(XNode node)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == W.t)
+ return new XElement(W.delText,
+ XmlUtil.GetXmlSpaceAttribute(element.Value),
+ element.Value);
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(TransformToDelText));
+ }
+
+ private static object PmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement,
+ Func<XElement, Match, bool> callback, ReplaceInternalInfo counter)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ if (element.Name == A.p)
+ {
+ XElement paragraph = element;
+ string contents = element.Descendants(A.t).Select(t => (string) t).StringConcatenate();
+ if (!regex.IsMatch(contents))
+ return new XElement(element.Name, element.Attributes(), element.Nodes());
+
+ var paragraphWithSplitRuns = new XElement(A.p,
+ paragraph.Attributes(),
+ paragraph.Nodes()
+ .Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter)));
+
+ List<XElement> runsTrimmed = paragraphWithSplitRuns
+ .Descendants(A.r)
+ .ToList();
+
+ var charsAndRuns = runsTrimmed
+ .Select(r =>
+ r.Element(A.t) != null
+ ? new { Ch = r.Element(A.t).Value, r }
+ : new { Ch = "\x01", r })
+ .ToList();
+
+ string content = charsAndRuns.Select(t => t.Ch).StringConcatenate();
+ XElement[] alignedRuns = charsAndRuns.Select(t => t.r).ToArray();
+
+ MatchCollection matchCollection = regex.Matches(content);
+ counter.Count += matchCollection.Count;
+ if (replacement == null)
+ {
+ foreach (Match match in matchCollection.Cast<Match>())
+ callback(paragraph, match);
+ }
+ else
+ {
+ foreach (Match match in matchCollection.Cast<Match>())
+ {
+ if ((callback != null) && !callback(paragraph, match)) continue;
+
+ List<XElement> runCollection = alignedRuns
+ .Skip(match.Index)
+ .Take(match.Length)
+ .ToList();
+
+ // uses the Skip / Take special semantics of array to implement efficient finding of sub array
+
+ XElement firstRun = runCollection.First();
+
+ // save away first run because we want the run properties
+
+ runCollection.Skip(1).Remove();
+
+ // binds to Remove(this IEnumerable<XElement> elements), which is an extension
+
+ // in LINQ to XML that uses snapshot semantics and removes every element from
+ // its parent.
+
+ var newFirstRun = new XElement(A.r,
+ firstRun.Element(A.rPr),
+ new XElement(A.t, replacement));
+
+ // creates a new run with proper run properties
+
+ firstRun.ReplaceWith(newFirstRun);
+
+ // finds firstRun in its parent's list of children, unparents firstRun,
+
+ // sets newFirstRun's parent to firstRuns old parent, and inserts in the list
+ // of children at the right place.
+ }
+ XElement paragraphWithReplacedRuns = paragraphWithSplitRuns;
+
+ IEnumerable<IGrouping<string, XElement>> groupedAdjacentRunsWithIdenticalFormatting =
+ paragraphWithReplacedRuns
+ .Elements()
+ .GroupAdjacent(ce =>
+ {
+ if (ce.Name != A.r)
+ return DontConsolidate;
+ if ((ce.Elements().Count(e => e.Name != A.rPr) != 1) || (ce.Element(A.t) == null))
+ return DontConsolidate;
+
+ XElement rPr = ce.Element(A.rPr);
+ return rPr == null ? "" : rPr.ToString(SaveOptions.None);
+ });
+ var paragraphWithConsolidatedRuns = new XElement(A.p,
+ groupedAdjacentRunsWithIdenticalFormatting.Select(g =>
+ {
+ if (g.Key == DontConsolidate)
+ return (object) g;
+
+ string textValue = g.Select(r => r.Element(A.t).Value).StringConcatenate();
+ XAttribute xs = XmlUtil.GetXmlSpaceAttribute(textValue);
+ return new XElement(A.r,
+ g.First().Elements(A.rPr),
+ new XElement(A.t, xs, textValue));
+ }));
+ paragraph = paragraphWithConsolidatedRuns;
+ }
+
+ return paragraph;
+ }
+
+ if ((element.Name == A.r) && element.Elements(A.t).Any())
+ {
+ return element.Elements()
+ .Where(e => e.Name != A.rPr)
+ .Select(e =>
+ {
+ if (e.Name == A.t)
+ {
+ var s = (string) e;
+ IEnumerable<XElement> collectionOfSubRuns = s.Select(c => new XElement(A.r,
+ element.Elements(A.rPr),
+ new XElement(A.t, XmlUtil.GetXmlSpaceAttribute(c), c)));
+ return (object) collectionOfSubRuns;
+ }
+
+ return new XElement(A.r,
+ element.Elements(A.rPr),
+ e);
+ });
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter)));
+ }
+
+ private class ReplaceInternalInfo
+ {
+ public int Count;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/OxPtHelpers.cs b/OpenXmlPowerTools/OxPtHelpers.cs
new file mode 100644
index 0000000..70528a9
--- /dev/null
+++ b/OpenXmlPowerTools/OxPtHelpers.cs
@@ -0,0 +1,694 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using System.Text;
+using DocumentFormat.OpenXml;
+using System.Drawing.Imaging;
+
+namespace OpenXmlPowerTools
+{
+ public static class AddDocxTextHelper
+ {
+ public static WmlDocument AppendParagraphToDocument(
+ WmlDocument wmlDoc,
+ string strParagraph,
+ bool isBold,
+ bool isItalic,
+ bool isUnderline,
+ string foreColor,
+ string backColor,
+ string styleName)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(wmlDoc))
+ {
+ using (WordprocessingDocument wDoc = streamDoc.GetWordprocessingDocument())
+ {
+ StyleDefinitionsPart part = wDoc.MainDocumentPart.StyleDefinitionsPart;
+
+ Body body = wDoc.MainDocumentPart.Document.Body;
+
+ SectionProperties sectionProperties = body.Elements<SectionProperties>().FirstOrDefault();
+
+ Paragraph paragraph = new Paragraph();
+ Run run = paragraph.AppendChild(new Run());
+ RunProperties runProperties = new RunProperties();
+
+ if (isBold)
+ runProperties.AppendChild(new Bold());
+
+ if (isItalic)
+ runProperties.AppendChild(new Italic());
+
+
+ if (!string.IsNullOrEmpty(foreColor))
+ {
+ int colorValue = System.Drawing.Color.FromName(foreColor).ToArgb();
+ if (colorValue == 0)
+ throw new OpenXmlPowerToolsException(String.Format("Add-DocxText: The specified color {0} is unsupported, Please specify the valid color. Ex, Red, Green", foreColor));
+
+ string ColorHex = string.Format("{0:x6}", colorValue);
+ runProperties.AppendChild(new DocumentFormat.OpenXml.Wordprocessing.Color() { Val = ColorHex.Substring(2) });
+ }
+
+ if (isUnderline)
+ runProperties.AppendChild(new Underline() { Val = UnderlineValues.Single });
+
+ if (!string.IsNullOrEmpty(backColor))
+ {
+ int colorShade = System.Drawing.Color.FromName(backColor).ToArgb();
+ if (colorShade == 0)
+ throw new OpenXmlPowerToolsException(String.Format("Add-DocxText: The specified color {0} is unsupported, Please specify the valid color. Ex, Red, Green", foreColor));
+
+ string ColorShadeHex = string.Format("{0:x6}", colorShade);
+ runProperties.AppendChild(new Shading() { Fill = ColorShadeHex.Substring(2), Val = ShadingPatternValues.Clear });
+ }
+
+ if (!string.IsNullOrEmpty(styleName))
+ {
+ Style style = part.Styles.Elements<Style>().Where(s => s.StyleId == styleName).FirstOrDefault();
+ //if the specified style is not present in word document add it
+ if (style == null)
+ {
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ #region Default.dotx Template has been used to get all the paragraph styles
+ string base64 =
+ @"UEsDBBQABgAIAAAAIQDTMB8uXgEAACAFAAATAAAAW0NvbnRlbnRfVHlwZXNdLnhtbLSUy27CMBBF
+95X6D5G3VWLooqoqAos+li1S6QcYewJW/ZI9vP6+EwKoqiCRCmwiJTP33jNWxoPR2ppsCTFp70rW
+L3osAye90m5Wsq/JW/7IsoTCKWG8g5JtILHR8PZmMNkESBmpXSrZHDE8cZ7kHKxIhQ/gqFL5aAXS
+a5zxIOS3mAG/7/UeuPQOwWGOtQcbDl6gEguD2euaPjckEUxi2XPTWGeVTIRgtBRIdb506k9Kvkso
+SLntSXMd0h01MH40oa6cDtjpPuhoolaQjUXEd2Gpi698VFx5ubCkLNptjnD6qtISDvraLUQvISU6
+c2sKBBtoAiis0G7Pf5Ij4cZAujxF49sdD4gkuAbAzrkTYQXTz6tR/DLvBKkodyKmBi6PcbDuhEDa
+QGie/bM5tjZtkdQ5jj4k2uj4j7H3K1urcxo4QETd/tcdEsn67Pmgvg0UqCPZfHu/DX8AAAD//wMA
+UEsDBBQABgAIAAAAIQAekRq37wAAAE4CAAALAAAAX3JlbHMvLnJlbHOsksFqwzAMQO+D/YPRvVHa
+wRijTi9j0NsY2QcIW0lME9vYatf+/TzY2AJd6WFHy9LTk9B6c5xGdeCUXfAallUNir0J1vlew1v7
+vHgAlYW8pTF41nDiDJvm9mb9yiNJKcqDi1kVis8aBpH4iJjNwBPlKkT25acLaSIpz9RjJLOjnnFV
+1/eYfjOgmTHV1mpIW3sHqj1FvoYdus4ZfgpmP7GXMy2Qj8Lesl3EVOqTuDKNain1LBpsMC8lnJFi
+rAoa8LzR6nqjv6fFiYUsCaEJiS/7fGZcElr+54rmGT827yFZtF/hbxucXUHzAQAA//8DAFBLAwQU
+AAYACADRagZB/Fz9fNYBAAALAwAAEAAAAGRvY1Byb3BzL2FwcC54bWztvQdgHEmWJSYvbcp7f0r1
+StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n
+99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyL+x7/3H3z8e7xblOllXjdFtfzso93xzkdpvpxWs2J5
+8dlH6/Z8++CjtGmz5Swrq2X+2UfXefPR73H0OFs9ellXq7xui7xJCcayeXTZfvbRvG1Xj+7ebabz
+fJE1Y2qxpC/Pq3qRtfRnfXG3Oj8vpvnTarpe5Mv27t7Ozqd3Z9UU0JqffHO9IvgKL1t9XXj5uzZf
+zvLZ9sri+BHj/CZfrMqszY8e3w3+wh9Vm5VvikV+tCNf2r95sNlF3hzt8jfyOz79blXPGm0vv+PT
+k3lWZ9OWaKpfeR/g++PVqiymWUsUP/qimNZVU5236Zc8jhRg+CW/Fd6iEb7Op+u6aK8VrP8JWjwv
+lrnpUn4XzOvsos5Wc/OV9wG+fz3NyvyE6HR0npVNzk3cZwr3bfPV6k31FLRyrcLPw5F/t2jnr1fZ
+1CIU/Yr7py/yGY3F799+hhbfJqaoS3RGQJYX+cxr2f9OKfyTwtJHu/fHO/QYkpqPhRKWO47+H1BL
+AwQUAAYACADRagZBUsP9QroBAABvAgAAEQAAAGRvY1Byb3BzL2NvcmUueG1s7b0HYBxJliUmL23K
+e39K9UrX4HShCIBgEyTYkEAQ7MGIzeaS7B1pRyMpqyqBymVWZV1mFkDM7Z28995777333nvvvfe6
+O51OJ/ff/z9cZmQBbPbOStrJniGAqsgfP358Hz8iHv8e7xZlepnXTVEtP/tod7zzUZovp9WsWF58
+9tFXb55tH3yUNm22nGVltcw/++g6bz76PY6Sx9PVo2lV5y/rapXXbZE3KQFaNo+mq88+mrft6tHd
+u810ni+yZkwtlvTleVUvspb+rC/urrLp2+wiv7u3s/Pp3UXeZrOsze4C4PbKQvxIQc6mFuRqXZcM
+YDa9m5f5Il+2zd3d8e5d17bN60UTfYG/8VouivZ6lUebmi9t63dNYRteXV2Nr+5xU8J/9+7v/cXz
+1zzU7WIJUk3zj44ez6aPpnWetVV9dJIVbVks0+NmXubX26+qslxky8d3vSYgZ5k17RdE+PMinz25
+vsuf1fllgZk52n181//zsY5TAOSzlPB7JKMx33z33snTN88+Otrb2d3b3nmwvXf/ze6DR3v3H+3s
+/BT6Dt53ABeKwW0g3nuzt/tovwPRADhijEMeOfp/AFBLAwQUAAYACAAAACEA1mSzUfQAAAAxAwAA
+HAAAAHdvcmQvX3JlbHMvZG9jdW1lbnQueG1sLnJlbHOskstqwzAQRfeF/oOYfS07fVBC5GxKIdvW
+/QBFHj+oLAnN9OG/r0hJ69BguvByrphzz4A228/BineM1HunoMhyEOiMr3vXKnipHq/uQRBrV2vr
+HSoYkWBbXl5sntBqTkvU9YFEojhS0DGHtZRkOhw0ZT6gSy+Nj4PmNMZWBm1edYtyled3Mk4ZUJ4w
+xa5WEHf1NYhqDPgftm+a3uCDN28DOj5TIT9w/4zM6ThKWB1bZAWTMEtEkOdFVkuK0B+LYzKnUCyq
+wKPFqcBhnqu/XbKe0y7+th/G77CYc7hZ0qHxjiu9txOPn+goIU8+evkFAAD//wMAUEsDBBQABgAI
+ANFqBkF65TN3MwIAAJAFAAARAAAAd29yZC9kb2N1bWVudC54bWztvQdgHEmWJSYvbcp7f0r1Stfg
+dKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//
+P1xmZAFs9s5K2smeIYCqyB8/fnwfPyL+x7/3H3z8e7xblOllXjdFtfzso93xzkdpvpxWs2J58dlH
+6/Z8++CjtGmz5Swrq2X+2UfXefPR73H0+OrRrJquF/myTQnAsnl0tZp+9tG8bVeP7t5tpvN8kTXj
+RTGtq6Y6b8fTanG3Oj8vpvndq6qe3d3b2d3h31Z1Nc2bhno7yZaXWfORglv0oVWrfElfnlf1Imvp
+z/ri7iKr365X2wR9lbXFpCiL9ppg73xqwFQ0hnr5SEFsW4TwyiNBSH+YN+rb9CuvPFUKcI9367wk
+HKplMy9WbhhfFxp9OTdALjcN4nJRmnZXq939D5uDp3V2RT8cwNugP5OXFqVgvhni7s4tZgQg7Bu3
+QSHs02CyyIql6/hrkcYj7u799wOw1wWwung/AN3J+byu1isHrfgwaGfLtxYW5Po9YOkk+0Nr3gtA
+D5nX82xFEriYPjq7WFZ1NikJI5qylKiegq0/gsaZVLNr/Fyld/Gjyafty5o/uHj9g/QKrLK7t7dP
+Guzq0Zx+v3+A3+9Kiy+ymj5uK2Lp3X1pUxcX89b9Oanatlq4v8v83Pt2nmeznJTDgz3+87yqWu/P
+i3XLf5r+plXZ0MfNKpvm2og+v+uQvmuGc9dp0qP/B1BLAwQUAAYACADRagZB3iZxVZoCAAAzBwAA
+EgAAAHdvcmQvZm9udFRhYmxlLnhtbO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3m
+kuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrI
+Hz9+fB8/Iv7Hv/cffPx7vFuU6WVeN0W1/Oyj3fHOR2m+nFazYnnx2Ufr9nz74KO0abPlLCurZf7Z
+R9d589HvcfT46tF5tWyblN5eNo8W088+mrft6tHdu810ni+yZlyt8iV9eV7Vi6ylP+uLu4usfrte
+bU+rxSpri0lRFu313b2dnU8/UjD1baBU5+fFNH9aTdeLfNny+3frvCSI1bKZF6vGQLu6DbSrqp6t
+6mqaNw2NeFEKvEVWLC2Y3f0eoEUxraumOm/HNBjFiEHR67s7/NuidADuvx+APQtgMX10drGs6mxS
+EukJk5SAfWSon149WmYL+uIkK4tJXfAXq2xZNfkufXeZlZ99tLO382znPv2L//Z37uHfj9K7aDmd
+Z3WTt7bljn5+ni2K8tp83FwVTaPfrIp2OjdfXGZ1Abz0u6a4oG/WzWTns49Od+jZe/bsI/lk97OP
+9umD4xP7yR6642dXP7lnP9nBJ1OGIy0ePtNPdv021OldIUOPHK+Lxev1kqmRle0L+szg/J//DX/s
+f/b3/6lmND1K7e58SrDv0U/9L06pg0+jlMrWbfWehNLR3HOE2js4eGaI4BNq99MbCAUK774noY4J
+sXKAa54QLfaVb/Z+aFyzd+xzzQl98uBg35DHcc3Dm7nm2ftyjQpR+ry4mLeDonTPkOOHJErHwHvv
+tCNKezsPnvSIYnhmmCg77y1Kb4pF3qQv8qv0VbXIlgNk2SNeuUdaZp81zb33JEvNkN+PLD9sXtFf
+mqP/B1BLAwQUAAQACADRagZBhoJdSR0EAAB5CQAAEQAAAHdvcmQvc2V0dGluZ3MueG1s7b0HYBxJ
+liUmL23Ke39K9UrX4HShCIBgEyTYkEAQ7MGIzeaS7B1pRyMpqyqBymVWZV1mFkDM7Z2899577733
+3nvvvfe6O51OJ/ff/z9cZmQBbPbOStrJniGAqsgfP358Hz8i/se/9x98/Hu8W5TpZV43RbX87KPd
+8c5Hab6cVrNiefHZR+v2fPvgo7Rps+UsK6tl/tlH13nz0e9x9PjqUZO3LTVqUgKwbB4tpp99NG/b
+1aO7d5vpPF9kzbha5Uv68ryqF1lLf9YXdxdZ/Xa92p5Wi1XWFpOiLNrru3s7O59+pGAq6rRePlIQ
+24tiWldNdd7ilUfV+XkxzfWHeaO+Tb/yytNqul7ky5Z7vFvnJeFQLZt5sWoMtMXXhUZfzg2Qy02D
+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
+AAsACwDBAgAAbCwAAAAA";
+ #endregion
+
+ char[] base64CharArray = base64.Where(c => c != '\r' && c != '\n').ToArray();
+ byte[] byteArray = System.Convert.FromBase64CharArray(base64CharArray, 0, base64CharArray.Length);
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+
+ using (WordprocessingDocument defaultDotx = WordprocessingDocument.Open(memoryStream, true))
+ {
+ //Get the specified style from Default.dotx template for paragraph
+ Style templateStyle = defaultDotx.MainDocumentPart.StyleDefinitionsPart.Styles.Elements<Style>().Where(s => s.StyleId == styleName && s.Type == StyleValues.Paragraph).FirstOrDefault();
+
+ //Check if the style is proper style. Ex, Heading1, Heading2
+ if (templateStyle == null)
+ throw new OpenXmlPowerToolsException(String.Format("Add-DocxText: The specified style name {0} is unsupported, Please specify the valid style. Ex, Heading1, Heading2, Title", styleName));
+ else
+ part.Styles.Append((templateStyle.CloneNode(true)));
+ }
+ }
+ }
+
+ paragraph.ParagraphProperties = new ParagraphProperties(new ParagraphStyleId() { Val = styleName });
+ }
+
+ run.AppendChild(runProperties);
+ run.AppendChild(new Text(strParagraph));
+
+ if (sectionProperties != null)
+ body.InsertBefore(paragraph, sectionProperties);
+ else
+ body.AppendChild(paragraph);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+ }
+
+ public class HtmlConverterHelper
+ {
+ public static void ConvertToHtml(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+ var pageTitle = (string)wDoc.CoreFilePropertiesPart.GetXDocument().Descendants(DC.title).FirstOrDefault();
+ if (pageTitle == null)
+ pageTitle = fi.FullName;
+
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
+ {
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ {
+ // Convert png to jpeg.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageFileName),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement html = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+ }
+
+ public class ValidationHelper
+ {
+ public static bool IsValid(string fileName, string officeVersion)
+ {
+#if !NET35
+ FileFormatVersions fileFormatVersion = FileFormatVersions.Office2013;
+#else
+ FileFormatVersions fileFormatVersion = FileFormatVersions.Office2010;
+#endif
+ try
+ {
+ fileFormatVersion = (FileFormatVersions)Enum.Parse(fileFormatVersion.GetType(), officeVersion);
+ }
+ catch (Exception)
+ {
+#if !NET35
+ fileFormatVersion = FileFormatVersions.Office2013;
+#else
+ fileFormatVersion = FileFormatVersions.Office2010;
+#endif
+ }
+
+ FileInfo fi = new FileInfo(fileName);
+ if (Util.IsWordprocessingML(fi.Extension))
+ {
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(fileName, false))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(fileFormatVersion);
+ var errors = validator.Validate(wDoc);
+ bool valid = errors.Count() == 0;
+ return valid;
+ }
+ }
+ else if (Util.IsSpreadsheetML(fi.Extension))
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(fileFormatVersion);
+ var errors = validator.Validate(sDoc);
+ bool valid = errors.Count() == 0;
+ return valid;
+ }
+ }
+ else if (Util.IsPresentationML(fi.Extension))
+ {
+ using (PresentationDocument pDoc = PresentationDocument.Open(fileName, false))
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(fileFormatVersion);
+ var errors = validator.Validate(pDoc);
+ bool valid = errors.Count() == 0;
+ return valid;
+ }
+ }
+ return false;
+ }
+
+ public static IEnumerable<ValidationErrorInfo> GetOpenXmlValidationErrors(string fileName,
+ string officeVersion)
+ {
+#if !NET35
+ FileFormatVersions fileFormatVersion = FileFormatVersions.Office2013;
+#else
+ FileFormatVersions fileFormatVersion = FileFormatVersions.Office2010;
+#endif
+ try
+ {
+ fileFormatVersion = (FileFormatVersions)Enum.Parse(fileFormatVersion.GetType(), officeVersion);
+ }
+ catch (Exception)
+ {
+#if !NET35
+ fileFormatVersion = FileFormatVersions.Office2013;
+#else
+ fileFormatVersion = FileFormatVersions.Office2010;
+#endif
+ }
+
+ FileInfo fi = new FileInfo(fileName);
+ if (Util.IsWordprocessingML(fi.Extension))
+ {
+ WmlDocument wml = new WmlDocument(fileName);
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(wml))
+ using (WordprocessingDocument wDoc = streamDoc.GetWordprocessingDocument())
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(fileFormatVersion);
+ var errors = validator.Validate(wDoc);
+ return errors.ToList();
+ }
+ }
+ else if (Util.IsSpreadsheetML(fi.Extension))
+ {
+ SmlDocument Sml = new SmlDocument(fileName);
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(Sml))
+ using (SpreadsheetDocument wDoc = streamDoc.GetSpreadsheetDocument())
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(fileFormatVersion);
+ var errors = validator.Validate(wDoc);
+ return errors.ToList();
+ }
+ }
+ else if (Util.IsPresentationML(fi.Extension))
+ {
+ PmlDocument Pml = new PmlDocument(fileName);
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(Pml))
+ using (PresentationDocument wDoc = streamDoc.GetPresentationDocument())
+ {
+ OpenXmlValidator validator = new OpenXmlValidator(fileFormatVersion);
+ var errors = validator.Validate(wDoc);
+ return errors.ToList();
+ }
+ }
+ return Enumerable.Empty<ValidationErrorInfo>();
+ }
+ }
+
+ public class DocxMetrics
+ {
+ public string FileName;
+
+ public int ActiveX;
+ public int AltChunk;
+ public int AsciiCharCount;
+ public int AsciiRunCount;
+ public int AverageParagraphLength;
+ public int ComplexField;
+ public int ContentControlCount;
+ public XmlDocument ContentControls;
+ public int CSCharCount;
+ public int CSRunCount;
+ public bool DocumentProtection;
+ public int EastAsiaCharCount;
+ public int EastAsiaRunCount;
+ public int ElementCount;
+ public bool EmbeddedXlsx;
+ public int HAnsiCharCount;
+ public int HAnsiRunCount;
+ public int Hyperlink;
+ public bool InvalidSaveThroughXslt;
+ public string Languages;
+ public int LegacyFrame;
+ public int MultiFontRun;
+ public string NumberingFormatList;
+ public int ReferenceToNullImage;
+ public bool RevisionTracking;
+ public int RunCount;
+ public int SimpleField;
+ public XmlDocument StyleHierarchy;
+ public int SubDocument;
+ public int Table;
+ public int TextBox;
+ public bool TrackRevisionsEnabled;
+ public bool Valid;
+ public int ZeroLengthText;
+ }
+
+ public static class GetMetricsHelper
+ {
+ public static DocxMetrics GetDocxMetrics(string fileName)
+ {
+ WmlDocument wmlDoc = new WmlDocument(fileName);
+ MetricsGetterSettings settings = new MetricsGetterSettings();
+ settings.IncludeTextInContentControls = false;
+ settings.IncludeXlsxTableCellData = false;
+ var metricsXml = MetricsGetter.GetDocxMetrics(wmlDoc, settings);
+ DocxMetrics metrics = new DocxMetrics();
+ metrics.FileName = wmlDoc.FileName;
+
+ metrics.StyleHierarchy = GetXmlDocumentForMetrics(metricsXml, H.StyleHierarchy);
+ metrics.ContentControls = GetXmlDocumentForMetrics(metricsXml, H.Parts);
+ metrics.TextBox = GetIntForMetrics(metricsXml, H.TextBox);
+ metrics.ContentControlCount = GetIntForMetrics(metricsXml, H.ContentControl);
+ metrics.ComplexField = GetIntForMetrics(metricsXml, H.ComplexField);
+ metrics.SimpleField = GetIntForMetrics(metricsXml, H.SimpleField);
+ metrics.AltChunk = GetIntForMetrics(metricsXml, H.AltChunk);
+ metrics.Table = GetIntForMetrics(metricsXml, H.Table);
+ metrics.Hyperlink = GetIntForMetrics(metricsXml, H.Hyperlink);
+ metrics.LegacyFrame = GetIntForMetrics(metricsXml, H.LegacyFrame);
+ metrics.ActiveX = GetIntForMetrics(metricsXml, H.ActiveX);
+ metrics.SubDocument = GetIntForMetrics(metricsXml, H.SubDocument);
+ metrics.ReferenceToNullImage = GetIntForMetrics(metricsXml, H.ReferenceToNullImage);
+ metrics.ElementCount = GetIntForMetrics(metricsXml, H.ElementCount);
+ metrics.AverageParagraphLength = GetIntForMetrics(metricsXml, H.AverageParagraphLength);
+ metrics.RunCount = GetIntForMetrics(metricsXml, H.RunCount);
+ metrics.ZeroLengthText = GetIntForMetrics(metricsXml, H.ZeroLengthText);
+ metrics.MultiFontRun = GetIntForMetrics(metricsXml, H.MultiFontRun);
+ metrics.AsciiCharCount = GetIntForMetrics(metricsXml, H.AsciiCharCount);
+ metrics.CSCharCount = GetIntForMetrics(metricsXml, H.CSCharCount);
+ metrics.EastAsiaCharCount = GetIntForMetrics(metricsXml, H.EastAsiaCharCount);
+ metrics.HAnsiCharCount = GetIntForMetrics(metricsXml, H.HAnsiCharCount);
+ metrics.AsciiRunCount = GetIntForMetrics(metricsXml, H.AsciiRunCount);
+ metrics.CSRunCount = GetIntForMetrics(metricsXml, H.CSRunCount);
+ metrics.EastAsiaRunCount = GetIntForMetrics(metricsXml, H.EastAsiaRunCount);
+ metrics.HAnsiRunCount = GetIntForMetrics(metricsXml, H.HAnsiRunCount);
+ metrics.RevisionTracking = GetBoolForMetrics(metricsXml, H.RevisionTracking);
+ metrics.EmbeddedXlsx = GetBoolForMetrics(metricsXml, H.EmbeddedXlsx);
+ metrics.InvalidSaveThroughXslt = GetBoolForMetrics(metricsXml, H.InvalidSaveThroughXslt);
+ metrics.TrackRevisionsEnabled = GetBoolForMetrics(metricsXml, H.TrackRevisionsEnabled);
+ metrics.DocumentProtection = GetBoolForMetrics(metricsXml, H.DocumentProtection);
+ metrics.Valid = GetBoolForMetrics(metricsXml, H.Valid);
+ metrics.Languages = GetStringForMetrics(metricsXml, H.Languages);
+ metrics.NumberingFormatList = GetStringForMetrics(metricsXml, H.NumberingFormatList);
+
+ return metrics;
+ }
+
+ private static string GetStringForMetrics(XElement metricsXml, XName xName)
+ {
+ var ele = metricsXml.Element(xName);
+ if (ele == null)
+ return "";
+ return (string)ele.Attribute(H.Val);
+ }
+
+ private static bool GetBoolForMetrics(XElement metricsXml, XName xName)
+ {
+ var ele = metricsXml.Element(xName);
+ if (ele == null)
+ return false;
+ return (bool)ele.Attribute(H.Val);
+ }
+
+ private static int GetIntForMetrics(XElement metricsXml, XName xName)
+ {
+ var ele = metricsXml.Element(xName);
+ if (ele == null)
+ return 0;
+ return (int)ele.Attribute(H.Val);
+ }
+
+ private static XmlDocument GetXmlDocumentForMetrics(XElement metricsXml, XName xName)
+ {
+ var ele = metricsXml.Element(xName);
+ if (ele == null)
+ return null;
+ return (new XDocument(metricsXml.Element(xName))).GetXmlDocument();
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/PegBase.cs b/OpenXmlPowerTools/PegBase.cs
new file mode 100644
index 0000000..4b22b1f
--- /dev/null
+++ b/OpenXmlPowerTools/PegBase.cs
@@ -0,0 +1,2106 @@
+/*Author:Martin.Holzherr;Date:20080922;Context:"PEG Support for C#";Licence:CPOL
+ * <<History>>
+ * 20080922;V1.0 created
+ * 20080929;UTF16BE;Added UTF16BE read support to <<FileLoader.LoadFile(out string src)>>
+ * <</History>>
+*/
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Diagnostics;
+using System.Text;
+using System;
+namespace Peg.Base
+{
+ #region Input File Support
+ public enum EncodingClass { unicode, utf8, binary, ascii };
+ public enum UnicodeDetection { notApplicable, BOM, FirstCharIsAscii };
+ public class FileLoader
+ {
+ public enum FileEncoding { none, ascii, binary, utf8, unicode, utf16be, utf16le, utf32le, utf32be, uniCodeBOM };
+ public FileLoader(EncodingClass encodingClass, UnicodeDetection detection, string path)
+ {
+ encoding_ = GetEncoding(encodingClass, detection, path);
+ path_ = path;
+ }
+ public bool IsBinaryFile()
+ {
+ return encoding_ == FileEncoding.binary;
+ }
+ public bool LoadFile(out byte[] src)
+ {
+ src = null;
+ if (!IsBinaryFile()) return false;
+ using (BinaryReader brdr = new BinaryReader(File.Open(path_, FileMode.Open,FileAccess.Read)))
+ {
+ src = brdr.ReadBytes((int)brdr.BaseStream.Length);
+ return true;
+ }
+ }
+ public bool LoadFile(out string src)
+ {
+ src = null;
+ Encoding textEncoding = FileEncodingToTextEncoding();
+ if (textEncoding == null)
+ {
+ if (encoding_ == FileEncoding.binary) return false;
+ using (StreamReader rd = new StreamReader(path_, true))
+ {
+ src = rd.ReadToEnd();
+ return true;
+ }
+ }
+ else
+ {
+ if (encoding_ == FileEncoding.utf16be)//UTF16BE
+ {
+ using (BinaryReader brdr = new BinaryReader(File.Open(path_, FileMode.Open, FileAccess.Read)))
+ {
+ byte[] bytes = brdr.ReadBytes((int)brdr.BaseStream.Length);
+ StringBuilder s = new StringBuilder();
+ for (int i = 0; i < bytes.Length; i += 2)
+ {
+ char c = (char)(bytes[i] << 8 | bytes[i + 1]);
+ s.Append(c);
+ }
+ src = s.ToString();
+ return true;
+ }
+ }
+ else
+ {
+ using (StreamReader rd = new StreamReader(path_, textEncoding))
+ {
+ src = rd.ReadToEnd();
+ return true;
+ }
+ }
+ }
+
+ }
+ Encoding FileEncodingToTextEncoding()
+ {
+ switch (encoding_)
+ {
+ case FileEncoding.utf8: return new UTF8Encoding();
+ case FileEncoding.utf32be:
+ case FileEncoding.utf32le: return new UTF32Encoding();
+ case FileEncoding.unicode:
+ case FileEncoding.utf16be:
+ case FileEncoding.utf16le: return new UnicodeEncoding();
+ case FileEncoding.ascii: return new ASCIIEncoding();
+ case FileEncoding.binary:
+ case FileEncoding.uniCodeBOM: return null;
+ default: Debug.Assert(false);
+ return null;
+
+ }
+ }
+ static FileEncoding DetermineUnicodeWhenFirstCharIsAscii(string path)
+ {
+ using (BinaryReader br = new BinaryReader(File.Open(path, FileMode.Open, FileAccess.Read)))
+ {
+ byte[] startBytes = br.ReadBytes(4);
+ if (startBytes.Length == 0) return FileEncoding.none;
+ if (startBytes.Length == 1 || startBytes.Length == 3) return FileEncoding.utf8;
+ if (startBytes.Length == 2 && startBytes[0] != 0) return FileEncoding.utf16le;
+ if (startBytes.Length == 2 && startBytes[0] == 0) return FileEncoding.utf16be;
+ if (startBytes[0] == 0 && startBytes[1] == 0) return FileEncoding.utf32be;
+ if (startBytes[0] == 0 && startBytes[1] != 0) return FileEncoding.utf16be;
+ if (startBytes[0] != 0 && startBytes[1] == 0) return FileEncoding.utf16le;
+ return FileEncoding.utf8;
+ }
+ }
+ FileEncoding GetEncoding(EncodingClass encodingClass, UnicodeDetection detection, string path)
+ {
+ switch (encodingClass)
+ {
+ case EncodingClass.ascii: return FileEncoding.ascii;
+ case EncodingClass.unicode:
+ {
+ if (detection == UnicodeDetection.FirstCharIsAscii)
+ {
+ return DetermineUnicodeWhenFirstCharIsAscii(path);
+ }
+ else if (detection == UnicodeDetection.BOM)
+ {
+ return FileEncoding.uniCodeBOM;
+ }
+ else return FileEncoding.unicode;
+ }
+ case EncodingClass.utf8: return FileEncoding.utf8;
+ case EncodingClass.binary: return FileEncoding.binary;
+ }
+ return FileEncoding.none;
+ }
+ string path_;
+ public readonly FileEncoding encoding_;
+ }
+ #endregion Input File Support
+ #region Error handling
+ public class PegException : System.Exception
+ {
+ public PegException()
+ : base("Fatal parsing error ocurred")
+ {
+ }
+ }
+ public struct PegError
+ {
+ internal SortedList<int, int> lineStarts;
+ void AddLineStarts(string s, int first, int last, ref int lineNo, out int colNo)
+ {
+ colNo = 2;
+ for (int i = first + 1; i <= last; ++i, ++colNo)
+ {
+ if (s[i - 1] == '\n')
+ {
+ lineStarts[i] = ++lineNo;
+ colNo = 1;
+ }
+ }
+ --colNo;
+ }
+ public void GetLineAndCol(string s, int pos, out int lineNo, out int colNo)
+ {
+ for (int i = lineStarts.Count(); i > 0; --i)
+ {
+ KeyValuePair<int, int> curLs = lineStarts.ElementAt(i - 1);
+ if (curLs.Key == pos)
+ {
+ lineNo = curLs.Value;
+ colNo = 1;
+ return;
+ }
+ if (curLs.Key < pos)
+ {
+ lineNo = curLs.Value;
+ AddLineStarts(s, curLs.Key, pos, ref lineNo, out colNo);
+ return;
+ }
+ }
+ lineNo = 1;
+ AddLineStarts(s, 0, pos, ref lineNo, out colNo);
+ }
+ }
+ #endregion Error handling
+ #region Syntax/Parse-Tree related classes
+ public enum ESpecialNodes { eFatal = -10001, eAnonymNTNode = -1000, eAnonymASTNode = -1001, eAnonymousNode = -100 }
+ public enum ECreatorPhase { eCreate, eCreationComplete, eCreateAndComplete }
+ public struct PegBegEnd//indices into the source string
+ {
+ public int Length
+ {
+ get { return posEnd_ - posBeg_; }
+ }
+ public string GetAsString(string src)
+ {
+ Debug.Assert(src.Length >= posEnd_);
+ return src.Substring(posBeg_, Length);
+ }
+ public int posBeg_;
+ public int posEnd_;
+ }
+ public class PegNode : ICloneable
+ {
+ #region Constructors
+ public PegNode(PegNode parent, int id, PegBegEnd match, PegNode child, PegNode next)
+ {
+ parent_ = parent; id_ = id; child_ = child; next_ = next;
+ match_ = match;
+ }
+ public PegNode(PegNode parent, int id, PegBegEnd match, PegNode child)
+ : this(parent, id, match, child, null)
+ {
+ }
+ public PegNode(PegNode parent, int id, PegBegEnd match)
+ : this(parent, id, match, null, null)
+ { }
+ public PegNode(PegNode parent, int id)
+ : this(parent, id, new PegBegEnd(), null, null)
+ {
+ }
+ #endregion Constructors
+ #region Public Members
+ public virtual string GetAsString(string s)
+ {
+ return match_.GetAsString(s);
+ }
+ public virtual PegNode Clone()
+ {
+ PegNode clone= new PegNode(parent_, id_, match_);
+ CloneSubTrees(clone);
+ return clone;
+ }
+ #endregion Public Members
+ #region Protected Members
+ protected void CloneSubTrees(PegNode clone)
+ {
+ PegNode child = null, next = null;
+ if (child_ != null)
+ {
+ child = child_.Clone();
+ child.parent_ = clone;
+ }
+ if (next_ != null)
+ {
+ next = next_.Clone();
+ next.parent_ = clone;
+ }
+ clone.child_ = child;
+ clone.next_ = next;
+ }
+ #endregion Protected Members
+ #region Data Members
+ public int id_;
+ public PegNode parent_, child_, next_;
+ public PegBegEnd match_;
+ #endregion Data Members
+
+ #region ICloneable Members
+
+ object ICloneable.Clone()
+ {
+ return Clone();
+ }
+
+ #endregion
+ }
+ internal struct PegTree
+ {
+ internal enum AddPolicy { eAddAsChild, eAddAsSibling };
+ internal PegNode root_;
+ internal PegNode cur_;
+ internal AddPolicy addPolicy;
+ }
+ public abstract class PrintNode
+ {
+ public abstract int LenMaxLine();
+ public abstract bool IsLeaf(PegNode p);
+ public virtual bool IsSkip(PegNode p) { return false; }
+ public abstract void PrintNodeBeg(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel);
+ public abstract void PrintNodeEnd(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel);
+ public abstract int LenNodeBeg(PegNode p);
+ public abstract int LenNodeEnd(PegNode p);
+ public abstract void PrintLeaf(PegNode p, ref int nOffsetLineBeg, bool bAlignVertical);
+ public abstract int LenLeaf(PegNode p);
+ public abstract int LenDistNext(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel);
+ public abstract void PrintDistNext(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel);
+ }
+ public class TreePrint : PrintNode
+ {
+ #region Data Members
+ public delegate string GetNodeName(PegNode node);
+ string src_;
+ TextWriter treeOut_;
+ int nMaxLineLen_;
+ bool bVerbose_;
+ GetNodeName GetNodeName_;
+ #endregion Data Members
+ #region Methods
+ public TreePrint(TextWriter treeOut, string src, int nMaxLineLen, GetNodeName GetNodeName, bool bVerbose)
+ {
+ treeOut_ = treeOut;
+ nMaxLineLen_ = nMaxLineLen;
+ bVerbose_ = bVerbose;
+ GetNodeName_ = GetNodeName;
+ src_ = src;
+ }
+
+ public void PrintTree(PegNode parent, int nOffsetLineBeg, int nLevel)
+ {
+ if (IsLeaf(parent))
+ {
+ PrintLeaf(parent, ref nOffsetLineBeg, false);
+ treeOut_.Flush();
+ return;
+ }
+ bool bAlignVertical =
+ DetermineLineLength(parent, nOffsetLineBeg) > LenMaxLine();
+ PrintNodeBeg(parent, bAlignVertical, ref nOffsetLineBeg, nLevel);
+ int nOffset = nOffsetLineBeg;
+ for (PegNode p = parent.child_; p != null; p = p.next_)
+ {
+ if (IsSkip(p)) continue;
+
+ if (IsLeaf(p))
+ {
+ PrintLeaf(p, ref nOffsetLineBeg, bAlignVertical);
+ }
+ else
+ {
+ PrintTree(p, nOffsetLineBeg, nLevel + 1);
+ }
+ if (bAlignVertical)
+ {
+ nOffsetLineBeg = nOffset;
+ }
+ while (p.next_ != null && IsSkip(p.next_)) p = p.next_;
+
+ if (p.next_ != null)
+ {
+ PrintDistNext(p, bAlignVertical, ref nOffsetLineBeg, nLevel);
+ }
+ }
+ PrintNodeEnd(parent, bAlignVertical, ref nOffsetLineBeg, nLevel);
+ treeOut_.Flush();
+ }
+ int DetermineLineLength(PegNode parent, int nOffsetLineBeg)
+ {
+ int nLen = LenNodeBeg(parent);
+ PegNode p;
+ for (p = parent.child_; p != null; p = p.next_)
+ {
+ if (IsSkip(p)) continue;
+ if (IsLeaf(p))
+ {
+ nLen += LenLeaf(p);
+ }
+ else
+ {
+ nLen += DetermineLineLength(p, nOffsetLineBeg);
+ }
+ if (nLen + nOffsetLineBeg > LenMaxLine())
+ {
+ return nLen + nOffsetLineBeg;
+ }
+ }
+ nLen += LenNodeEnd(p);
+ return nLen;
+ }
+ public override int LenMaxLine() { return nMaxLineLen_; }
+ public override void
+ PrintNodeBeg(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel)
+ {
+ PrintIdAsName(p);
+ treeOut_.Write("<");
+ if (bAlignVertical)
+ {
+ treeOut_.WriteLine();
+ treeOut_.Write(new string(' ', nOffsetLineBeg += 2));
+ }
+ else
+ {
+ ++nOffsetLineBeg;
+ }
+ }
+ public override void
+ PrintNodeEnd(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel)
+ {
+ if (bAlignVertical)
+ {
+ treeOut_.WriteLine();
+ treeOut_.Write(new string(' ', nOffsetLineBeg -= 2));
+ }
+ treeOut_.Write('>');
+ if (!bAlignVertical)
+ {
+ ++nOffsetLineBeg;
+ }
+ }
+ public override int LenNodeBeg(PegNode p) { return LenIdAsName(p) + 1; }
+ public override int LenNodeEnd(PegNode p) { return 1; }
+ public override void PrintLeaf(PegNode p, ref int nOffsetLineBeg, bool bAlignVertical)
+ {
+ if (bVerbose_)
+ {
+ PrintIdAsName(p);
+ treeOut_.Write('<');
+ }
+ int len = p.match_.posEnd_ - p.match_.posBeg_;
+ treeOut_.Write("'");
+ if (len > 0)
+ {
+ treeOut_.Write(src_.Substring(p.match_.posBeg_, p.match_.posEnd_ - p.match_.posBeg_));
+ }
+ treeOut_.Write("'");
+ if (bVerbose_) treeOut_.Write('>');
+ }
+ public override int LenLeaf(PegNode p)
+ {
+ int nLen = p.match_.posEnd_ - p.match_.posBeg_ + 2;
+ if (bVerbose_) nLen += LenIdAsName(p) + 2;
+ return nLen;
+ }
+ public override bool IsLeaf(PegNode p)
+ {
+ return p.child_ == null;
+ }
+
+ public override void
+ PrintDistNext(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel)
+ {
+ if (bAlignVertical)
+ {
+ treeOut_.WriteLine();
+ treeOut_.Write(new string(' ', nOffsetLineBeg));
+ }
+ else
+ {
+ treeOut_.Write(' ');
+ ++nOffsetLineBeg;
+ }
+ }
+
+ public override int
+ LenDistNext(PegNode p, bool bAlignVertical, ref int nOffsetLineBeg, int nLevel)
+ {
+ return 1;
+ }
+ int LenIdAsName(PegNode p)
+ {
+ string name = GetNodeName_(p);
+ return name.Length;
+ }
+ void PrintIdAsName(PegNode p)
+ {
+ string name = GetNodeName_(p);
+ treeOut_.Write(name);
+ }
+ #endregion Methods
+ }
+ #endregion Syntax/Parse-Tree related classes
+ #region Parsers
+ public abstract class PegBaseParser
+ {
+ #region Data Types
+ public delegate bool Matcher();
+ public delegate PegNode Creator(ECreatorPhase ePhase, PegNode parentOrCreated, int id);
+ #endregion Data Types
+ #region Data members
+ protected int srcLen_;
+ protected int pos_;
+ protected bool bMute_;
+ protected TextWriter errOut_;
+ protected Creator nodeCreator_;
+ protected int maxpos_;
+ PegTree tree;
+ #endregion Data members
+ public virtual string GetRuleNameFromId(int id)
+ {//normally overridden
+ switch (id)
+ {
+ case (int)ESpecialNodes.eFatal: return "FATAL";
+ case (int)ESpecialNodes.eAnonymNTNode: return "Nonterminal";
+ case (int)ESpecialNodes.eAnonymASTNode: return "ASTNode";
+ case (int)ESpecialNodes.eAnonymousNode: return "Node";
+ default: return id.ToString();
+ }
+ }
+ public virtual void GetProperties(out EncodingClass encoding, out UnicodeDetection detection)
+ {
+ encoding = EncodingClass.ascii;
+ detection = UnicodeDetection.notApplicable;
+ }
+ public int GetMaximumPosition()
+ {
+ return maxpos_;
+ }
+ protected PegNode DefaultNodeCreator(ECreatorPhase phase, PegNode parentOrCreated, int id)
+ {
+ if (phase == ECreatorPhase.eCreate || phase == ECreatorPhase.eCreateAndComplete)
+ return new PegNode(parentOrCreated, id);
+ else
+ {
+ if (parentOrCreated.match_.posEnd_ > maxpos_)
+ maxpos_ = parentOrCreated.match_.posEnd_;
+ return null;
+ }
+ }
+ #region Constructors
+ public PegBaseParser(TextWriter errOut)
+ {
+ srcLen_ = pos_ = 0;
+ errOut_ = errOut;
+ nodeCreator_ = DefaultNodeCreator;
+ }
+ #endregion Constructors
+ #region Reinitialization, TextWriter access,Tree Access
+ public void Construct(TextWriter Fout)
+ {
+ srcLen_ = pos_ = 0;
+ bMute_ = false;
+ SetErrorDestination(Fout);
+ ResetTree();
+ }
+ public void Rewind() { pos_ = 0; }
+ public void SetErrorDestination(TextWriter errOut)
+ {
+ errOut_ = errOut == null ? new StreamWriter(System.Console.OpenStandardError())
+ : errOut;
+ }
+ #endregion Reinitialization, TextWriter access,Tree Access
+ #region Tree root access, Tree Node generation/display
+ public PegNode GetRoot() { return tree.root_; }
+ public void ResetTree()
+ {
+ tree.root_ = null;
+ tree.cur_ = null;
+ tree.addPolicy = PegTree.AddPolicy.eAddAsChild;
+ }
+ void AddTreeNode(int nId, PegTree.AddPolicy newAddPolicy, Creator createNode, ECreatorPhase ePhase)
+ {
+ if (bMute_) return;
+ if (tree.root_ == null)
+ {
+ tree.root_ = tree.cur_ = createNode(ePhase, tree.cur_, nId);
+ }
+ else if (tree.addPolicy == PegTree.AddPolicy.eAddAsChild)
+ {
+ tree.cur_ = tree.cur_.child_ = createNode(ePhase, tree.cur_, nId);
+ }
+ else
+ {
+ tree.cur_ = tree.cur_.next_ = createNode(ePhase, tree.cur_.parent_, nId);
+ }
+ tree.addPolicy = newAddPolicy;
+ }
+ void RestoreTree(PegNode prevCur, PegTree.AddPolicy prevPolicy)
+ {
+ if (bMute_) return;
+ if (prevCur == null)
+ {
+ tree.root_ = null;
+ }
+ else if (prevPolicy == PegTree.AddPolicy.eAddAsChild)
+ {
+ prevCur.child_ = null;
+ }
+ else
+ {
+ prevCur.next_ = null;
+ }
+ tree.cur_ = prevCur;
+ tree.addPolicy = prevPolicy;
+ }
+ public bool TreeChars(Matcher toMatch)
+ {
+ return TreeCharsWithId((int)ESpecialNodes.eAnonymousNode, toMatch);
+ }
+ public bool TreeChars(Creator nodeCreator, Matcher toMatch)
+ {
+ return TreeCharsWithId(nodeCreator, (int)ESpecialNodes.eAnonymousNode, toMatch);
+ }
+ public bool TreeCharsWithId(int nId, Matcher toMatch)
+ {
+ return TreeCharsWithId(nodeCreator_, nId, toMatch);
+ }
+ public bool TreeCharsWithId(Creator nodeCreator, int nId, Matcher toMatch)
+ {
+ int pos = pos_;
+ if (toMatch())
+ {
+ if (!bMute_)
+ {
+ AddTreeNode(nId, PegTree.AddPolicy.eAddAsSibling, nodeCreator, ECreatorPhase.eCreateAndComplete);
+ tree.cur_.match_.posBeg_ = pos;
+ tree.cur_.match_.posEnd_ = pos_;
+ }
+ return true;
+ }
+ return false;
+ }
+ public bool TreeNT(int nRuleId, Matcher toMatch)
+ {
+ return TreeNT(nodeCreator_, nRuleId, toMatch);
+ }
+ public bool TreeNT(Creator nodeCreator, int nRuleId, Matcher toMatch)
+ {
+ if (bMute_) return toMatch();
+ PegNode prevCur = tree.cur_, ruleNode;
+ PegTree.AddPolicy prevPolicy = tree.addPolicy;
+ int posBeg = pos_;
+ AddTreeNode(nRuleId, PegTree.AddPolicy.eAddAsChild, nodeCreator, ECreatorPhase.eCreate);
+ ruleNode = tree.cur_;
+ bool bMatches = toMatch();
+ if (!bMatches) RestoreTree(prevCur, prevPolicy);
+ else
+ {
+ ruleNode.match_.posBeg_ = posBeg;
+ ruleNode.match_.posEnd_ = pos_;
+ tree.cur_ = ruleNode;
+ tree.addPolicy = PegTree.AddPolicy.eAddAsSibling;
+ nodeCreator(ECreatorPhase.eCreationComplete, ruleNode, nRuleId);
+ }
+ return bMatches;
+ }
+ public bool TreeAST(int nRuleId, Matcher toMatch)
+ {
+ return TreeAST(nodeCreator_, nRuleId, toMatch);
+ }
+ public bool TreeAST(Creator nodeCreator, int nRuleId, Matcher toMatch)
+ {
+ if (bMute_) return toMatch();
+ bool bMatches = TreeNT(nodeCreator, nRuleId, toMatch);
+ if (bMatches)
+ {
+ if (tree.cur_.child_ != null && tree.cur_.child_.next_ == null && tree.cur_.parent_ != null)
+ {
+ if (tree.cur_.parent_.child_ == tree.cur_)
+ {
+ tree.cur_.parent_.child_ = tree.cur_.child_;
+ tree.cur_.child_.parent_ = tree.cur_.parent_;
+ tree.cur_ = tree.cur_.child_;
+ }
+ else
+ {
+ PegNode prev;
+ for (prev = tree.cur_.parent_.child_; prev != null && prev.next_ != tree.cur_; prev = prev.next_)
+ {
+ }
+ if (prev != null)
+ {
+ prev.next_ = tree.cur_.child_;
+ tree.cur_.child_.parent_ = tree.cur_.parent_;
+ tree.cur_ = tree.cur_.child_;
+ }
+ }
+ }
+ }
+ return bMatches;
+ }
+ public bool TreeNT(Matcher toMatch)
+ {
+ return TreeNT((int)ESpecialNodes.eAnonymNTNode, toMatch);
+ }
+ public bool TreeNT(Creator nodeCreator, Matcher toMatch)
+ {
+ return TreeNT(nodeCreator, (int)ESpecialNodes.eAnonymNTNode, toMatch);
+ }
+ public bool TreeAST(Matcher toMatch)
+ {
+ return TreeAST((int)ESpecialNodes.eAnonymASTNode, toMatch);
+ }
+ public bool TreeAST(Creator nodeCreator, Matcher toMatch)
+ {
+ return TreeAST(nodeCreator, (int)ESpecialNodes.eAnonymASTNode, toMatch);
+ }
+ public virtual string TreeNodeToString(PegNode node)
+ {
+ return GetRuleNameFromId(node.id_);
+ }
+ public void SetNodeCreator(Creator nodeCreator)
+ {
+ Debug.Assert(nodeCreator != null);
+ nodeCreator_ = nodeCreator;
+ }
+ #endregion Tree Node generation
+ #region PEG e1 e2 .. ; &e1 ; !e1 ; e? ; e* ; e+ ; e{a,b} ; .
+ public bool And(Matcher pegSequence)
+ {
+ PegNode prevCur = tree.cur_;
+ PegTree.AddPolicy prevPolicy = tree.addPolicy;
+ int pos0 = pos_;
+ bool bMatches = pegSequence();
+ if (!bMatches)
+ {
+ pos_ = pos0;
+ RestoreTree(prevCur, prevPolicy);
+ }
+ return bMatches;
+ }
+ public bool Peek(Matcher toMatch)
+ {
+ int pos0 = pos_;
+ bool prevMute = bMute_;
+ bMute_ = true;
+ bool bMatches = toMatch();
+ bMute_ = prevMute;
+ pos_ = pos0;
+ return bMatches;
+ }
+ public bool Not(Matcher toMatch)
+ {
+ int pos0 = pos_;
+ bool prevMute = bMute_;
+ bMute_ = true;
+ bool bMatches = toMatch();
+ bMute_ = prevMute;
+ pos_ = pos0;
+ return !bMatches;
+ }
+ public bool PlusRepeat(Matcher toRepeat)
+ {
+ int i;
+ for (i = 0; ; ++i)
+ {
+ int pos0 = pos_;
+ if (!toRepeat())
+ {
+ pos_ = pos0;
+ break;
+ }
+ }
+ return i > 0;
+ }
+ public bool OptRepeat(Matcher toRepeat)
+ {
+ for (; ; )
+ {
+ int pos0 = pos_;
+ if (!toRepeat())
+ {
+ pos_ = pos0;
+ return true;
+ }
+ }
+ }
+ public bool Option(Matcher toMatch)
+ {
+ int pos0 = pos_;
+ if (!toMatch()) pos_ = pos0;
+ return true;
+ }
+ public bool ForRepeat(int count, Matcher toRepeat)
+ {
+ PegNode prevCur = tree.cur_;
+ PegTree.AddPolicy prevPolicy = tree.addPolicy;
+ int pos0 = pos_;
+ int i;
+ for (i = 0; i < count; ++i)
+ {
+ if (!toRepeat())
+ {
+ pos_ = pos0;
+ RestoreTree(prevCur, prevPolicy);
+ return false;
+ }
+ }
+ return true;
+ }
+ public bool ForRepeat(int lower, int upper, Matcher toRepeat)
+ {
+ PegNode prevCur = tree.cur_;
+ PegTree.AddPolicy prevPolicy = tree.addPolicy;
+ int pos0 = pos_;
+ int i;
+ for (i = 0; i < upper; ++i)
+ {
+ if (!toRepeat()) break;
+ }
+ if (i < lower)
+ {
+ pos_ = pos0;
+ RestoreTree(prevCur, prevPolicy);
+ return false;
+ }
+ return true;
+ }
+ public bool Any()
+ {
+ if (pos_ < srcLen_)
+ {
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ #endregion PEG e1 e2 .. ; &e1 ; !e1 ; e? ; e* ; e+ ; e{a,b} ; .
+ }
+ public class PegByteParser : PegBaseParser
+ {
+ #region Data members
+ protected byte[] src_;
+ PegError errors;
+ #endregion Data members
+
+ #region PEG optimizations
+ public sealed class BytesetData
+ {
+ public struct Range
+ {
+ public Range(byte low, byte high) { this.low = low; this.high = high; }
+ public byte low;
+ public byte high;
+ }
+ System.Collections.BitArray charSet_;
+ bool bNegated_;
+ public BytesetData(System.Collections.BitArray b)
+ : this(b, false)
+ {
+ }
+ public BytesetData(System.Collections.BitArray b, bool bNegated)
+ {
+ charSet_ = new System.Collections.BitArray(b);
+ bNegated_ = bNegated;
+ }
+ public BytesetData(Range[] r, byte[] c)
+ : this(r, c, false)
+ {
+ }
+ public BytesetData(Range[] r, byte[] c, bool bNegated)
+ {
+ int max = 0;
+ if (r != null) foreach (Range val in r) if (val.high > max) max = val.high;
+ if (c != null) foreach (int val in c) if (val > max) max = val;
+ charSet_ = new System.Collections.BitArray(max + 1, false);
+ if (r != null)
+ {
+ foreach (Range val in r)
+ {
+ for (int i = val.low; i <= val.high; ++i)
+ {
+ charSet_[i] = true;
+ }
+ }
+ }
+ if (c != null) foreach (int val in c) charSet_[val] = true;
+ bNegated_ = bNegated;
+ }
+ public bool Matches(byte c)
+ {
+ bool bMatches = c < charSet_.Length && charSet_[(int)c];
+ if (bNegated_) return !bMatches;
+ else return bMatches;
+ }
+ }
+ /* public class BytesetData
+ {
+ public struct Range
+ {
+ public Range(byte low, byte high) { this.low = low; this.high = high; }
+ public byte low;
+ public byte high;
+ }
+ protected System.Collections.BitArray charSet_;
+ bool bNegated_;
+ public BytesetData(System.Collections.BitArray b, bool bNegated)
+ {
+ charSet_ = new System.Collections.BitArray(b);
+ bNegated_ = bNegated;
+ }
+ public BytesetData(byte[] c, bool bNegated)
+ {
+ int max = 0;
+ foreach (int val in c) if (val > max) max = val;
+ charSet_ = new System.Collections.BitArray(max + 1, false);
+ foreach (int val in c) charSet_[val] = true;
+ bNegated_ = bNegated;
+ }
+ public BytesetData(Range[] r, byte[] c, bool bNegated)
+ {
+ int max = 0;
+ foreach (Range val in r) if (val.high > max) max = val.high;
+ foreach (int val in c) if (val > max) max = val;
+ charSet_ = new System.Collections.BitArray(max + 1, false);
+ foreach (Range val in r)
+ {
+ for (int i = val.low; i <= val.high; ++i)
+ {
+ charSet_[i] = true;
+ }
+ }
+ foreach (int val in c) charSet_[val] = true;
+ }
+
+
+ public bool Matches(byte c)
+ {
+ bool bMatches = c < charSet_.Length && charSet_[(int)c];
+ if (bNegated_) return !bMatches;
+ else return bMatches;
+ }
+ }*/
+ #endregion PEG optimizations
+ #region Constructors
+ public PegByteParser()
+ : this(null)
+ {
+ }
+ public PegByteParser(byte[] src):base(null)
+ {
+ SetSource(src);
+ }
+ public PegByteParser(byte[] src, TextWriter errOut):base(errOut)
+ {
+ SetSource(src);
+ }
+ #endregion Constructors
+ #region Reinitialization, Source Code access, TextWriter access,Tree Access
+ public void Construct(byte[] src, TextWriter Fout)
+ {
+ base.Construct(Fout);
+ SetSource(src);
+ }
+ public void SetSource(byte[] src)
+ {
+ if (src == null) src = new byte[0];
+ src_ = src; srcLen_ = src.Length;
+ errors.lineStarts = new SortedList<int, int>();
+ errors.lineStarts[0] = 1;
+ }
+ public byte[] GetSource() { return src_; }
+
+ #endregion Reinitialization, Source Code access, TextWriter access,Tree Access
+ #region Setting host variables
+ public bool Into(Matcher toMatch,out byte[] into)
+ {
+ int pos = pos_;
+ if (toMatch())
+ {
+ int nLen = pos_ - pos;
+ into= new byte[nLen];
+ for(int i=0;i<nLen;++i){
+ into[i] = src_[i+pos];
+ }
+ return true;
+ }
+ else
+ {
+ into = null;
+ return false;
+ }
+ }
+ public bool Into(Matcher toMatch,out PegBegEnd begEnd)
+ {
+ begEnd.posBeg_ = pos_;
+ bool bMatches = toMatch();
+ begEnd.posEnd_ = pos_;
+ return bMatches;
+ }
+ public bool Into(Matcher toMatch,out int into)
+ {
+ byte[] s;
+ into = 0;
+ if (!Into(toMatch,out s)) return false;
+ into = 0;
+ for (int i = 0; i < s.Length; ++i)
+ {
+ into <<= 8;
+ into |= s[i];
+ }
+ return true;
+ }
+ public bool Into(Matcher toMatch,out double into)
+ {
+ byte[] s;
+ into = 0.0;
+ if (!Into(toMatch,out s)) return false;
+ System.Text.Encoding encoding = System.Text.Encoding.UTF8;
+ string sAsString = encoding.GetString(s);
+ if (!System.Double.TryParse(sAsString, out into)) return false;
+ return true;
+ }
+ public bool BitsInto(int lowBitNo, int highBitNo,out int into)
+ {
+ if (pos_ < srcLen_)
+ {
+ into = (src_[pos_] >> (lowBitNo - 1)) & ((1 << highBitNo) - 1);
+ ++pos_;
+ return true;
+ }
+ into = 0;
+ return false;
+ }
+ public bool BitsInto(int lowBitNo, int highBitNo, BytesetData toMatch, out int into)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte value = (byte)((src_[pos_] >> (lowBitNo - 1)) & ((1 << highBitNo) - 1));
+ ++pos_;
+ into = value;
+ return toMatch.Matches(value);
+ }
+ into = 0;
+ return false;
+ }
+ #endregion Setting host variables
+ #region Error handling
+ void LogOutMsg(string sErrKind, string sMsg)
+ {
+ errOut_.WriteLine("<{0}>{1}:{2}", pos_, sErrKind, sMsg);
+ errOut_.Flush();
+ }
+ public virtual bool Fatal(string sMsg)
+ {
+
+ LogOutMsg("FATAL", sMsg);
+ throw new PegException();
+ }
+ public bool Warning(string sMsg)
+ {
+ LogOutMsg("WARNING", sMsg);
+ return true;
+ }
+ #endregion Error handling
+ #region PEG Bit level equivalents for PEG e1 ; &e1 ; !e1; e1:into ;
+ public bool Bits(int lowBitNo, int highBitNo, byte toMatch)
+ {
+ if (pos_ < srcLen_ && ((src_[pos_] >> (lowBitNo - 1)) & ((1 << highBitNo) - 1)) == toMatch)
+ {
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool Bits(int lowBitNo, int highBitNo,BytesetData toMatch)
+ {
+ if( pos_ < srcLen_ )
+ {
+ byte value= (byte)((src_[pos_] >> (lowBitNo - 1)) & ((1 << highBitNo) - 1));
+ ++pos_;
+ return toMatch.Matches(value);
+ }
+ return false;
+ }
+ public bool PeekBits(int lowBitNo, int highBitNo, byte toMatch)
+ {
+ return pos_ < srcLen_ && ((src_[pos_] >> (lowBitNo - 1)) & ((1 << highBitNo) - 1)) == toMatch;
+ }
+ public bool NotBits(int lowBitNo, int highBitNo, byte toMatch)
+ {
+ return !(pos_ < srcLen_ && ((src_[pos_] >> (lowBitNo - 1)) & ((1 << highBitNo) - 1)) == toMatch);
+ }
+ public bool IntoBits(int lowBitNo,int highBitNo,out int val)
+ {
+ return BitsInto(lowBitNo,highBitNo,out val);
+ }
+ public bool IntoBits(int lowBitNo, int highBitNo, BytesetData toMatch, out int val)
+ {
+ return BitsInto(lowBitNo, highBitNo, out val);
+ }
+ public bool Bit(int bitNo,byte toMatch)
+ {
+ if (pos_ < srcLen_ && ((src_[pos_]>>(bitNo-1))&1)==toMatch){
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool PeekBit(int bitNo, byte toMatch)
+ {
+ return pos_ < srcLen_ && ((src_[pos_] >> (bitNo - 1)) & 1) == toMatch;
+ }
+ public bool NotBit(int bitNo, byte toMatch)
+ {
+ return !(pos_ < srcLen_ && ((src_[pos_] >> (bitNo - 1)) & 1) == toMatch);
+ }
+ #endregion PEG Bit level equivalents for PEG e1 ; &e1 ; !e1; e1:into ;
+ #region PEG '<Literal>' / '<Literal>'/i / [low1-high1,low2-high2..] / [<CharList>]
+ public bool Char(byte c1)
+ {
+ if (pos_ < srcLen_ && src_[pos_] == c1)
+ { ++pos_; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2)
+ {
+ if (pos_ + 1 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2)
+ { pos_ += 2; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2, byte c3)
+ {
+ if (pos_ + 2 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3)
+ { pos_ += 3; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2, byte c3, byte c4)
+ {
+ if (pos_ + 3 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4)
+ { pos_ += 4; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2, byte c3, byte c4, byte c5)
+ {
+ if (pos_ + 4 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5)
+ { pos_ += 5; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2, byte c3, byte c4, byte c5, byte c6)
+ {
+ if (pos_ + 5 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5
+ && src_[pos_ + 5] == c6)
+ { pos_ += 6; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2, byte c3, byte c4, byte c5, byte c6, byte c7)
+ {
+ if (pos_ + 6 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5
+ && src_[pos_ + 5] == c6
+ && src_[pos_ + 6] == c7)
+ { pos_ += 7; return true; }
+ return false;
+ }
+ public bool Char(byte c1, byte c2, byte c3, byte c4, byte c5, byte c6, byte c7, byte c8)
+ {
+ if (pos_ + 7 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5
+ && src_[pos_ + 5] == c6
+ && src_[pos_ + 6] == c7
+ && src_[pos_ + 7] == c8)
+ { pos_ += 8; return true; }
+ return false;
+ }
+ public bool Char(byte[] s)
+ {
+ int sLength = s.Length;
+ if (pos_ + sLength > srcLen_) return false;
+ for (int i = 0; i < sLength; ++i)
+ {
+ if (s[i] != src_[pos_ + i]) return false;
+ }
+ pos_ += sLength;
+ return true;
+ }
+ public static byte ToUpper(byte c)
+ {
+ if (c >= 97 && c <= 122) return (byte)(c - 32); else return c;
+ }
+ public bool IChar(byte c1)
+ {
+ if (pos_ < srcLen_ && ToUpper(src_[pos_]) == c1)
+ { ++pos_; return true; }
+ return false;
+ }
+ public bool IChar(byte c1, byte c2)
+ {
+ if (pos_ + 1 < srcLen_
+ && ToUpper(src_[pos_]) == ToUpper(c1)
+ && ToUpper(src_[pos_ + 1]) == ToUpper(c2))
+ { pos_ += 2; return true; }
+ return false;
+ }
+ public bool IChar(byte c1, byte c2, byte c3)
+ {
+ if (pos_ + 2 < srcLen_
+ && ToUpper(src_[pos_]) == ToUpper(c1)
+ && ToUpper(src_[pos_ + 1]) == ToUpper(c2)
+ && ToUpper(src_[pos_ + 2]) == ToUpper(c3))
+ { pos_ += 3; return true; }
+ return false;
+ }
+ public bool IChar(byte c1, byte c2, byte c3, byte c4)
+ {
+ if (pos_ + 3 < srcLen_
+ && ToUpper(src_[pos_]) == ToUpper(c1)
+ && ToUpper(src_[pos_ + 1]) == ToUpper(c2)
+ && ToUpper(src_[pos_ + 2]) == ToUpper(c3)
+ && ToUpper(src_[pos_ + 3]) == ToUpper(c4))
+ { pos_ += 4; return true; }
+ return false;
+ }
+ public bool IChar(byte c1, byte c2, byte c3, byte c4, byte c5)
+ {
+ if (pos_ + 4 < srcLen_
+ && ToUpper(src_[pos_]) == ToUpper(c1)
+ && ToUpper(src_[pos_ + 1]) == ToUpper(c2)
+ && ToUpper(src_[pos_ + 2]) == ToUpper(c3)
+ && ToUpper(src_[pos_ + 3]) == ToUpper(c4)
+ && ToUpper(src_[pos_ + 4]) == ToUpper(c5))
+ { pos_ += 5; return true; }
+ return false;
+ }
+ public bool IChar(byte c1, byte c2, byte c3, byte c4, byte c5, byte c6)
+ {
+ if (pos_ + 5 < srcLen_
+ && ToUpper(src_[pos_]) == ToUpper(c1)
+ && ToUpper(src_[pos_ + 1]) == ToUpper(c2)
+ && ToUpper(src_[pos_ + 2]) == ToUpper(c3)
+ && ToUpper(src_[pos_ + 3]) == ToUpper(c4)
+ && ToUpper(src_[pos_ + 4]) == ToUpper(c5)
+ && ToUpper(src_[pos_ + 5]) == ToUpper(c6))
+ { pos_ += 6; return true; }
+ return false;
+ }
+ public bool IChar(byte c1, byte c2, byte c3, byte c4, byte c5, byte c6, byte c7)
+ {
+ if (pos_ + 6 < srcLen_
+ && ToUpper(src_[pos_]) == ToUpper(c1)
+ && ToUpper(src_[pos_ + 1]) == ToUpper(c2)
+ && ToUpper(src_[pos_ + 2]) == ToUpper(c3)
+ && ToUpper(src_[pos_ + 3]) == ToUpper(c4)
+ && ToUpper(src_[pos_ + 4]) == ToUpper(c5)
+ && ToUpper(src_[pos_ + 5]) == ToUpper(c6)
+ && ToUpper(src_[pos_ + 6]) == ToUpper(c7))
+ { pos_ += 7; return true; }
+ return false;
+ }
+ public bool IChar(byte[] s)
+ {
+ int sLength = s.Length;
+ if (pos_ + sLength > srcLen_) return false;
+ for (int i = 0; i < sLength; ++i)
+ {
+ if (s[i] != ToUpper(src_[pos_ + i])) return false;
+ }
+ pos_ += sLength;
+ return true;
+ }
+ public bool In(byte c0, byte c1)
+ {
+ if (pos_ < srcLen_
+ && src_[pos_] >= c0 && src_[pos_] <= c1)
+ {
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool In(byte c0, byte c1, byte c2, byte c3)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c >= c0 && c <= c1
+ || c >= c2 && c <= c3)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool In(byte c0, byte c1, byte c2, byte c3, byte c4, byte c5)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c >= c0 && c <= c1
+ || c >= c2 && c <= c3
+ || c >= c4 && c <= c5)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool In(byte c0, byte c1, byte c2, byte c3, byte c4, byte c5, byte c6, byte c7)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c >= c0 && c <= c1
+ || c >= c2 && c <= c3
+ || c >= c4 && c <= c5
+ || c >= c6 && c <= c7)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool In(byte[] s)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ for (int i = 0; i < s.Length - 1; i += 2)
+ {
+ if (c >= s[i] && c <= s[i + 1])
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ public bool NotIn(byte[] s)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ for (int i = 0; i < s.Length - 1; i += 2)
+ {
+ if ( c >= s[i] && c <= s[i + 1] ) return false;
+ }
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1)
+ {
+ if (pos_ < srcLen_
+ && (src_[pos_] == c0 || src_[pos_] == c1))
+ {
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1, byte c2)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1, byte c2, byte c3)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1, byte c2, byte c3, byte c4)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1, byte c2, byte c3, byte c4, byte c5)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4 || c == c5)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1, byte c2, byte c3, byte c4, byte c5, byte c6)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4 || c == c5 || c == c6)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(byte c0, byte c1, byte c2, byte c3, byte c4, byte c5, byte c6, byte c7)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4 || c == c5 || c == c6 || c == c7)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(byte[] s)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ for (int i = 0; i < s.Length; ++i)
+ {
+ if (c == s[i]) { ++pos_; return true; }
+ }
+ }
+ return false;
+ }
+ public bool NotOneOf(byte[] s)
+ {
+ if (pos_ < srcLen_)
+ {
+ byte c = src_[pos_];
+ for (int i = 0; i < s.Length; ++i)
+ {
+ if (c == s[i]) { return false; }
+ }
+ return true;
+ }
+ return false;
+ }
+ public bool OneOf(BytesetData bset)
+ {
+ if(pos_ < srcLen_ && bset.Matches(src_[pos_]))
+ {
+ ++pos_; return true;
+ }
+ return false;
+ }
+ #endregion PEG '<Literal>' / '<Literal>'/i / [low1-high1,low2-high2..] / [<CharList>]
+ }
+ public class PegCharParser : PegBaseParser
+ {
+ #region Data members
+ protected string src_;
+ PegError errors;
+ #endregion Data members
+ #region PEG optimizations
+ public sealed class OptimizedCharset
+ {
+ public struct Range
+ {
+ public Range(char low, char high) { this.low = low; this.high = high; }
+ public char low;
+ public char high;
+ }
+ System.Collections.BitArray charSet_;
+ bool bNegated_;
+ public OptimizedCharset(System.Collections.BitArray b)
+ : this(b, false)
+ {
+ }
+ public OptimizedCharset(System.Collections.BitArray b, bool bNegated)
+ {
+ charSet_ = new System.Collections.BitArray(b);
+ bNegated_ = bNegated;
+ }
+ public OptimizedCharset(Range[] r, char[] c)
+ : this(r, c, false)
+ {
+ }
+ public OptimizedCharset(Range[] r, char[] c, bool bNegated)
+ {
+ int max = 0;
+ if (r != null) foreach (Range val in r) if (val.high > max) max = val.high;
+ if (c != null) foreach (int val in c) if (val > max) max = val;
+ charSet_ = new System.Collections.BitArray(max + 1, false);
+ if (r != null)
+ {
+ foreach (Range val in r)
+ {
+ for (int i = val.low; i <= val.high; ++i)
+ {
+ charSet_[i] = true;
+ }
+ }
+ }
+ if (c != null) foreach (int val in c) charSet_[val] = true;
+ bNegated_ = bNegated;
+ }
+
+
+ public bool Matches(char c)
+ {
+ bool bMatches = c < charSet_.Length && charSet_[(int)c];
+ if (bNegated_) return !bMatches;
+ else return bMatches;
+ }
+ }
+ public sealed class OptimizedLiterals
+ {
+ internal class Trie
+ {
+ internal Trie(char cThis,int nIndex, string[] literals)
+ {
+ cThis_ = cThis;
+ char cMax = char.MinValue;
+ cMin_ = char.MaxValue;
+ HashSet<char> followChars = new HashSet<char>();
+
+ foreach (string literal in literals)
+ {
+ if (literal==null || nIndex > literal.Length ) continue;
+ if (nIndex == literal.Length)
+ {
+ bLitEnd_ = true;
+ continue;
+ }
+ char c = literal[nIndex];
+ followChars.Add(c);
+ if ( c < cMin_) cMin_ = c;
+ if ( c > cMax) cMax = c;
+ }
+ if (followChars.Count == 0)
+ {
+ children_ = null;
+ }
+ else
+ {
+ children_ = new Trie[(cMax - cMin_) + 1];
+ foreach (char c in followChars)
+ {
+ List<string> subLiterals = new List<string>();
+ foreach (string s in literals)
+ {
+ if ( nIndex >= s.Length ) continue;
+ if (c == s[nIndex])
+ {
+ subLiterals.Add(s);
+ }
+ }
+ children_[c - cMin_] = new Trie(c, nIndex + 1, subLiterals.ToArray());
+ }
+ }
+
+ }
+ internal char cThis_; //character stored in this node
+ internal bool bLitEnd_; //end of literal
+
+ internal char cMin_; //first valid character in children
+ internal Trie[] children_; //contains the successor node of cThis_;
+ }
+ internal Trie literalsRoot;
+ public OptimizedLiterals(string[] litAlternatives)
+ {
+ literalsRoot = new Trie('\u0000', 0, litAlternatives);
+ }
+ }
+ #endregion PEG optimizations
+ #region Constructors
+ public PegCharParser():this("")
+ {
+
+
+ }
+ public PegCharParser(string src):base(null)
+ {
+ SetSource(src);
+ }
+ public PegCharParser(string src, TextWriter errOut):base(errOut)
+ {
+ SetSource(src);
+ nodeCreator_ = DefaultNodeCreator;
+ }
+ #endregion Constructors
+ #region Overrides
+ public override string TreeNodeToString(PegNode node)
+ {
+ string label = base.TreeNodeToString(node);
+ if (node.id_ == (int)ESpecialNodes.eAnonymousNode)
+ {
+ string value = node.GetAsString(src_);
+ if (value.Length < 32) label += " <" + value + ">";
+ else label += " <" + value.Substring(0, 29) + "...>";
+ }
+ return label;
+ }
+ #endregion Overrides
+ #region Reinitialization, Source Code access, TextWriter access,Tree Access
+ public void Construct(string src, TextWriter Fout)
+ {
+ base.Construct(Fout);
+ SetSource(src);
+ }
+ public void SetSource(string src)
+ {
+ if (src == null) src = "";
+ src_ = src; srcLen_ = src.Length; pos_ = 0;
+ errors.lineStarts = new SortedList<int, int>();
+ errors.lineStarts[0] = 1;
+ }
+ public string GetSource() { return src_; }
+ #endregion Reinitialization, Source Code access, TextWriter access,Tree Access
+ #region Setting host variables
+ public bool Into(Matcher toMatch,out string into)
+ {
+ int pos = pos_;
+ if (toMatch())
+ {
+ into = src_.Substring(pos, pos_ - pos);
+ return true;
+ }
+ else
+ {
+ into = "";
+ return false;
+ }
+ }
+ public bool Into(Matcher toMatch,out PegBegEnd begEnd)
+ {
+ begEnd.posBeg_ = pos_;
+ bool bMatches = toMatch();
+ begEnd.posEnd_ = pos_;
+ return bMatches;
+ }
+ public bool Into(Matcher toMatch,out int into)
+ {
+ string s;
+ into = 0;
+ if (!Into(toMatch,out s)) return false;
+ if (!System.Int32.TryParse(s, out into)) return false;
+ return true;
+ }
+ public bool Into(Matcher toMatch,out double into)
+ {
+ string s;
+ into = 0.0;
+ if (!Into(toMatch,out s)) return false;
+ if (!System.Double.TryParse(s, out into)) return false;
+ return true;
+ }
+ #endregion Setting host variables
+ #region Error handling
+ void LogOutMsg(string sErrKind, string sMsg)
+ {
+ int lineNo, colNo;
+ errors.GetLineAndCol(src_, pos_, out lineNo, out colNo);
+ errOut_.WriteLine("<{0},{1},{2}>{3}:{4}", lineNo, colNo, maxpos_, sErrKind, sMsg);
+ errOut_.Flush();
+ }
+ public virtual bool Fatal(string sMsg)
+ {
+
+ LogOutMsg("FATAL", sMsg);
+ throw new PegException();
+ //return false;
+ }
+ public bool Warning(string sMsg)
+ {
+ LogOutMsg("WARNING", sMsg);
+ return true;
+ }
+ #endregion Error handling
+ #region PEG optimized version of e* ; e+
+ public bool OptRepeat(OptimizedCharset charset)
+ {
+ for (; pos_ < srcLen_ && charset.Matches(src_[pos_]); ++pos_) ;
+ return true;
+ }
+ public bool PlusRepeat(OptimizedCharset charset)
+ {
+ int pos0 = pos_;
+ for (; pos_ < srcLen_ && charset.Matches(src_[pos_]); ++pos_) ;
+ return pos_ > pos0;
+ }
+ #endregion PEG optimized version of e* ; e+
+ #region PEG '<Literal>' / '<Literal>'/i / [low1-high1,low2-high2..] / [<CharList>]
+ public bool Char(char c1)
+ {
+ if (pos_ < srcLen_ && src_[pos_] == c1)
+ { ++pos_; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2)
+ {
+ if (pos_ + 1 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2)
+ { pos_ += 2; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2, char c3)
+ {
+ if (pos_ + 2 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3)
+ { pos_ += 3; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2, char c3, char c4)
+ {
+ if (pos_ + 3 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4)
+ { pos_ += 4; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2, char c3, char c4, char c5)
+ {
+ if (pos_ + 4 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5)
+ { pos_ += 5; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2, char c3, char c4, char c5, char c6)
+ {
+ if (pos_ + 5 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5
+ && src_[pos_ + 5] == c6)
+ { pos_ += 6; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2, char c3, char c4, char c5, char c6, char c7)
+ {
+ if (pos_ + 6 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5
+ && src_[pos_ + 5] == c6
+ && src_[pos_ + 6] == c7)
+ { pos_ += 7; return true; }
+ return false;
+ }
+ public bool Char(char c1, char c2, char c3, char c4, char c5, char c6, char c7, char c8)
+ {
+ if (pos_ + 7 < srcLen_
+ && src_[pos_] == c1
+ && src_[pos_ + 1] == c2
+ && src_[pos_ + 2] == c3
+ && src_[pos_ + 3] == c4
+ && src_[pos_ + 4] == c5
+ && src_[pos_ + 5] == c6
+ && src_[pos_ + 6] == c7
+ && src_[pos_ + 7] == c8)
+ { pos_ += 8; return true; }
+ return false;
+ }
+ public bool Char(string s)
+ {
+ int sLength = s.Length;
+ if (pos_ + sLength > srcLen_) return false;
+ for (int i = 0; i < sLength; ++i)
+ {
+ if (s[i] != src_[pos_ + i]) return false;
+ }
+ pos_ += sLength;
+ return true;
+ }
+ public bool IChar(char c1)
+ {
+ if (pos_ < srcLen_ && System.Char.ToUpper(src_[pos_]) == c1)
+ { ++pos_; return true; }
+ return false;
+ }
+ public bool IChar(char c1, char c2)
+ {
+ if (pos_ + 1 < srcLen_
+ && System.Char.ToUpper(src_[pos_]) == System.Char.ToUpper(c1)
+ && System.Char.ToUpper(src_[pos_ + 1]) == System.Char.ToUpper(c2))
+ { pos_ += 2; return true; }
+ return false;
+ }
+ public bool IChar(char c1, char c2, char c3)
+ {
+ if (pos_ + 2 < srcLen_
+ && System.Char.ToUpper(src_[pos_]) == System.Char.ToUpper(c1)
+ && System.Char.ToUpper(src_[pos_ + 1]) == System.Char.ToUpper(c2)
+ && System.Char.ToUpper(src_[pos_ + 2]) == System.Char.ToUpper(c3))
+ { pos_ += 3; return true; }
+ return false;
+ }
+ public bool IChar(char c1, char c2, char c3, char c4)
+ {
+ if (pos_ + 3 < srcLen_
+ && System.Char.ToUpper(src_[pos_]) == System.Char.ToUpper(c1)
+ && System.Char.ToUpper(src_[pos_ + 1]) == System.Char.ToUpper(c2)
+ && System.Char.ToUpper(src_[pos_ + 2]) == System.Char.ToUpper(c3)
+ && System.Char.ToUpper(src_[pos_ + 3]) == System.Char.ToUpper(c4))
+ { pos_ += 4; return true; }
+ return false;
+ }
+ public bool IChar(char c1, char c2, char c3, char c4, char c5)
+ {
+ if (pos_ + 4 < srcLen_
+ && System.Char.ToUpper(src_[pos_]) == System.Char.ToUpper(c1)
+ && System.Char.ToUpper(src_[pos_ + 1]) == System.Char.ToUpper(c2)
+ && System.Char.ToUpper(src_[pos_ + 2]) == System.Char.ToUpper(c3)
+ && System.Char.ToUpper(src_[pos_ + 3]) == System.Char.ToUpper(c4)
+ && System.Char.ToUpper(src_[pos_ + 4]) == System.Char.ToUpper(c5))
+ { pos_ += 5; return true; }
+ return false;
+ }
+ public bool IChar(char c1, char c2, char c3, char c4, char c5, char c6)
+ {
+ if (pos_ + 5 < srcLen_
+ && System.Char.ToUpper(src_[pos_]) == System.Char.ToUpper(c1)
+ && System.Char.ToUpper(src_[pos_ + 1]) == System.Char.ToUpper(c2)
+ && System.Char.ToUpper(src_[pos_ + 2]) == System.Char.ToUpper(c3)
+ && System.Char.ToUpper(src_[pos_ + 3]) == System.Char.ToUpper(c4)
+ && System.Char.ToUpper(src_[pos_ + 4]) == System.Char.ToUpper(c5)
+ && System.Char.ToUpper(src_[pos_ + 5]) == System.Char.ToUpper(c6))
+ { pos_ += 6; return true; }
+ return false;
+ }
+ public bool IChar(char c1, char c2, char c3, char c4, char c5, char c6, char c7)
+ {
+ if (pos_ + 6 < srcLen_
+ && System.Char.ToUpper(src_[pos_]) == System.Char.ToUpper(c1)
+ && System.Char.ToUpper(src_[pos_ + 1]) == System.Char.ToUpper(c2)
+ && System.Char.ToUpper(src_[pos_ + 2]) == System.Char.ToUpper(c3)
+ && System.Char.ToUpper(src_[pos_ + 3]) == System.Char.ToUpper(c4)
+ && System.Char.ToUpper(src_[pos_ + 4]) == System.Char.ToUpper(c5)
+ && System.Char.ToUpper(src_[pos_ + 5]) == System.Char.ToUpper(c6)
+ && System.Char.ToUpper(src_[pos_ + 6]) == System.Char.ToUpper(c7))
+ { pos_ += 7; return true; }
+ return false;
+ }
+ public bool IChar(string s)
+ {
+ int sLength = s.Length;
+ if (pos_ + sLength > srcLen_) return false;
+ for (int i = 0; i < sLength; ++i)
+ {
+ if (s[i] != System.Char.ToUpper(src_[pos_ + i])) return false;
+ }
+ pos_ += sLength;
+ return true;
+ }
+
+ public bool In(char c0, char c1)
+ {
+ if (pos_ < srcLen_
+ && src_[pos_] >= c0 && src_[pos_] <= c1)
+ {
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool In(char c0, char c1, char c2, char c3)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c >= c0 && c <= c1
+ || c >= c2 && c <= c3)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool In(char c0, char c1, char c2, char c3, char c4, char c5)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c >= c0 && c <= c1
+ || c >= c2 && c <= c3
+ || c >= c4 && c <= c5)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool In(char c0, char c1, char c2, char c3, char c4, char c5, char c6, char c7)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c >= c0 && c <= c1
+ || c >= c2 && c <= c3
+ || c >= c4 && c <= c5
+ || c >= c6 && c <= c7)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool In(string s)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ for (int i = 0; i < s.Length - 1; i += 2)
+ {
+ if (!(c >= s[i] && c <= s[i + 1])) return false;
+ }
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool NotIn(string s)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ for (int i = 0; i < s.Length - 1; i += 2)
+ {
+ if ( c >= s[i] && c <= s[i + 1]) return false;
+ }
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1)
+ {
+ if (pos_ < srcLen_
+ && (src_[pos_] == c0 || src_[pos_] == c1))
+ {
+ ++pos_;
+ return true;
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1, char c2)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1, char c2, char c3)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1, char c2, char c3, char c4)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1, char c2, char c3, char c4, char c5)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4 || c == c5)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1, char c2, char c3, char c4, char c5, char c6)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4 || c == c5 || c == c6)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(char c0, char c1, char c2, char c3, char c4, char c5, char c6, char c7)
+ {
+ if (pos_ < srcLen_)
+ {
+ char c = src_[pos_];
+ if (c == c0 || c == c1 || c == c2 || c == c3 || c == c4 || c == c5 || c == c6 || c == c7)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(string s)
+ {
+ if (pos_ < srcLen_)
+ {
+ if (s.IndexOf(src_[pos_]) != -1)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool NotOneOf(string s)
+ {
+ if (pos_ < srcLen_)
+ {
+ if (s.IndexOf(src_[pos_]) == -1)
+ {
+ ++pos_;
+ return true;
+ }
+ }
+ return false;
+ }
+ public bool OneOf(OptimizedCharset cset)
+ {
+ if (pos_ < srcLen_ && cset.Matches(src_[pos_]))
+ {
+ ++pos_; return true;
+ }
+ return false;
+ }
+ public bool OneOfLiterals(OptimizedLiterals litAlt)
+ {
+ OptimizedLiterals.Trie node = litAlt.literalsRoot;
+ int matchPos = pos_-1;
+ for (int pos = pos_; pos < srcLen_ ; ++pos)
+ {
+ char c = src_[pos];
+ if ( node.children_==null
+ || c < node.cMin_ || c > node.cMin_ + node.children_.Length - 1
+ || node.children_[c - node.cMin_] == null)
+ {
+ break;
+ }
+ node = node.children_[c - node.cMin_];
+ if (node.bLitEnd_) matchPos = pos + 1;
+ }
+ if (matchPos >= pos_)
+ {
+ pos_= matchPos;
+ return true;
+ }
+ else return false;
+ }
+ #endregion PEG '<Literal>' / '<Literal>'/i / [low1-high1,low2-high2..] / [<CharList>]
+ }
+ #endregion Parsers
+}
diff --git a/OpenXmlPowerTools/PowerToolsBlock.cs b/OpenXmlPowerTools/PowerToolsBlock.cs
new file mode 100644
index 0000000..b441859
--- /dev/null
+++ b/OpenXmlPowerTools/PowerToolsBlock.cs
@@ -0,0 +1,71 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ /// <summary>
+ /// Provides an elegant way of wrapping a set of invocations of the PowerTools in a using
+ /// statement that demarcates those invokations as one "block" before and after which the
+ /// strongly typed classes provided by the Open XML SDK can be used safely.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This class lends itself to scenarios where the PowerTools and Linq-to-XML are used as
+ /// a secondary API for working with Open XML elements, next to the strongly typed classes
+ /// provided by the Open XML SDK. In these scenarios, the class would be
+ /// used as follows:
+ /// </para>
+ /// <code>
+ /// [Your code using the strongly typed classes]
+ ///
+ /// using (new PowerToolsBlock(wordprocessingDocument))
+ /// {
+ /// [Your code using the PowerTools]
+ /// }
+ ///
+ /// [Your code using the strongly typed classes]
+ /// </code>
+ /// <para>
+ /// Upon creation, instances of this class will invoke the
+ /// <see cref="PowerToolsBlockExtensions.BeginPowerToolsBlock"/> method on the package
+ /// to begin the transaction. Upon disposal, instances of this class will call the
+ /// <see cref="PowerToolsBlockExtensions.EndPowerToolsBlock"/> method on the package
+ /// to end the transaction.
+ /// </para>
+ /// </remarks>
+ /// <seealso cref="StronglyTypedBlock" />
+ /// <seealso cref="PowerToolsBlockExtensions.BeginPowerToolsBlock"/>
+ /// <seealso cref="PowerToolsBlockExtensions.EndPowerToolsBlock"/>
+ public class PowerToolsBlock : IDisposable
+ {
+ private OpenXmlPackage _package;
+
+ public PowerToolsBlock(OpenXmlPackage package)
+ {
+ if (package == null) throw new ArgumentNullException("package");
+
+ _package = package;
+ _package.BeginPowerToolsBlock();
+ }
+
+ public void Dispose()
+ {
+ if (_package == null) return;
+
+ _package.EndPowerToolsBlock();
+ _package = null;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/PowerToolsBlockExtensions.cs b/OpenXmlPowerTools/PowerToolsBlockExtensions.cs
new file mode 100644
index 0000000..9305150
--- /dev/null
+++ b/OpenXmlPowerTools/PowerToolsBlockExtensions.cs
@@ -0,0 +1,79 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public static class PowerToolsBlockExtensions
+ {
+ /// <summary>
+ /// Begins a PowerTools Block by (1) removing annotations and, unless the package was
+ /// opened in read-only mode, (2) saving the package.
+ /// </summary>
+ /// <remarks>
+ /// Removes <see cref="XDocument" /> and <see cref="XmlNamespaceManager" /> instances
+ /// added by <see cref="PtOpenXmlExtensions.GetXDocument(OpenXmlPart)" />,
+ /// <see cref="PtOpenXmlExtensions.GetXDocument(OpenXmlPart, out XmlNamespaceManager)" />,
+ /// <see cref="PtOpenXmlExtensions.PutXDocument(OpenXmlPart)" />,
+ /// <see cref="PtOpenXmlExtensions.PutXDocument(OpenXmlPart, XDocument)" />, and
+ /// <see cref="PtOpenXmlExtensions.PutXDocumentWithFormatting(OpenXmlPart)" />.
+ /// methods.
+ /// </remarks>
+ /// <param name="package">
+ /// A <see cref="WordprocessingDocument" />, <see cref="SpreadsheetDocument" />,
+ /// or <see cref="PresentationDocument" />.
+ /// </param>
+ public static void BeginPowerToolsBlock(this OpenXmlPackage package)
+ {
+ if (package == null) throw new ArgumentNullException("package");
+
+ package.RemovePowerToolsAnnotations();
+ package.Save();
+ }
+
+ /// <summary>
+ /// Ends a PowerTools Block by reloading the root elements of all package parts
+ /// that were changed by the PowerTools. A part is deemed changed by the PowerTools
+ /// if it has an annotation of type <see cref="XDocument" />.
+ /// </summary>
+ /// <param name="package">
+ /// A <see cref="WordprocessingDocument" />, <see cref="SpreadsheetDocument" />,
+ /// or <see cref="PresentationDocument" />.
+ /// </param>
+ public static void EndPowerToolsBlock(this OpenXmlPackage package)
+ {
+ if (package == null) throw new ArgumentNullException("package");
+
+ foreach (OpenXmlPart part in package.GetAllParts())
+ {
+ if (part.Annotations<XDocument>().Any() && part.RootElement != null)
+ part.RootElement.Reload();
+ }
+ }
+
+ private static void RemovePowerToolsAnnotations(this OpenXmlPackage package)
+ {
+ if (package == null) throw new ArgumentNullException("package");
+
+ foreach (OpenXmlPart part in package.GetAllParts())
+ {
+ part.RemoveAnnotations<XDocument>();
+ part.RemoveAnnotations<XmlNamespaceManager>();
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/PresentationBuilder.cs b/OpenXmlPowerTools/PresentationBuilder.cs
new file mode 100644
index 0000000..d10683c
--- /dev/null
+++ b/OpenXmlPowerTools/PresentationBuilder.cs
@@ -0,0 +1,1849 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 2.5.01
+ * Fix issue where a specific relationship was not being created.
+
+Version: 2.5.00
+ * Fix to optimize storing of images when the same image is on more than one slide.
+
+Version: 2.4.00
+ * Update to PresentationBuilder to make it much more robust.
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class SlideSource
+ {
+ public PmlDocument PmlDocument { get; set; }
+ public int Start { get; set; }
+ public int Count { get; set; }
+ public bool KeepMaster { get; set; }
+
+ public SlideSource(PmlDocument source, bool keepMaster)
+ {
+ PmlDocument = source;
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepMaster = keepMaster;
+ }
+
+ public SlideSource(string fileName, bool keepMaster)
+ {
+ PmlDocument = new PmlDocument(fileName);
+ Start = 0;
+ Count = Int32.MaxValue;
+ KeepMaster = keepMaster;
+ }
+
+ public SlideSource(PmlDocument source, int start, bool keepMaster)
+ {
+ PmlDocument = source;
+ Start = start;
+ Count = Int32.MaxValue;
+ KeepMaster = keepMaster;
+ }
+
+ public SlideSource(string fileName, int start, bool keepMaster)
+ {
+ PmlDocument = new PmlDocument(fileName);
+ Start = start;
+ Count = Int32.MaxValue;
+ KeepMaster = keepMaster;
+ }
+
+ public SlideSource(PmlDocument source, int start, int count, bool keepMaster)
+ {
+ PmlDocument = source;
+ Start = start;
+ Count = count;
+ KeepMaster = keepMaster;
+ }
+
+ public SlideSource(string fileName, int start, int count, bool keepMaster)
+ {
+ PmlDocument = new PmlDocument(fileName);
+ Start = start;
+ Count = count;
+ KeepMaster = keepMaster;
+ }
+ }
+
+ public static class PresentationBuilder
+ {
+ public static void BuildPresentation(List<SlideSource> sources, string fileName)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreatePresentationDocument())
+ {
+ using (PresentationDocument output = streamDoc.GetPresentationDocument())
+ {
+ BuildPresentation(sources, output);
+ output.Close();
+ }
+ streamDoc.GetModifiedDocument().SaveAs(fileName);
+ }
+ }
+
+ public static PmlDocument BuildPresentation(List<SlideSource> sources)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreatePresentationDocument())
+ {
+ using (PresentationDocument output = streamDoc.GetPresentationDocument())
+ {
+ BuildPresentation(sources, output);
+ output.Close();
+ }
+ return streamDoc.GetModifiedPmlDocument();
+ }
+ }
+
+ private static void BuildPresentation(List<SlideSource> sources, PresentationDocument output)
+ {
+ if (RelationshipMarkup == null)
+ RelationshipMarkup = new Dictionary<XName, XName[]>()
+ {
+ { A.audioFile, new [] { R.link }},
+ { A.videoFile, new [] { R.link }},
+ { A.quickTimeFile, new [] { R.link }},
+ { A.wavAudioFile, new [] { R.embed }},
+ { A.blip, new [] { R.embed, R.link }},
+ { A.hlinkClick, new [] { R.id }},
+ { A.hlinkMouseOver, new [] { R.id }},
+ { A.hlinkHover, new [] { R.id }},
+ { A.relIds, new [] { R.cs, R.dm, R.lo, R.qs }},
+ { C.chart, new [] { R.id }},
+ { C.externalData, new [] { R.id }},
+ { C.userShapes, new [] { R.id }},
+ { DGM.relIds, new [] { R.cs, R.dm, R.lo, R.qs }},
+ { A14.imgLayer, new [] { R.embed }},
+ { P14.media, new [] { R.embed, R.link }},
+ { P.oleObj, new [] { R.id }},
+ { P.externalData, new [] { R.id }},
+ { P.control, new [] { R.id }},
+ { P.snd, new [] { R.embed }},
+ { P.sndTgt, new [] { R.embed }},
+ { PAV.srcMedia, new [] { R.embed, R.link }},
+ { P.contentPart, new [] { R.id }},
+ { VML.fill, new [] { R.id }},
+ { VML.imagedata, new [] { R.href, R.id, R.pict, O.relid }},
+ { VML.stroke, new [] { R.id }},
+ { WNE.toolbarData, new [] { R.id }},
+ { Plegacy.textdata, new [] { XName.Get("id") }},
+ };
+
+ List<ImageData> images = new List<ImageData>();
+ List<MediaData> mediaList = new List<MediaData>();
+ XDocument mainPart = output.PresentationPart.GetXDocument();
+ mainPart.Declaration.Standalone = "yes";
+ mainPart.Declaration.Encoding = "UTF-8";
+ output.PresentationPart.PutXDocument();
+
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(sources[0].PmlDocument))
+ using (PresentationDocument doc = streamDoc.GetPresentationDocument())
+ {
+ CopyStartingParts(doc, output);
+ }
+
+ int sourceNum = 0;
+ SlideMasterPart currentMasterPart = null;
+ foreach (SlideSource source in sources)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(source.PmlDocument))
+ using (PresentationDocument doc = streamDoc.GetPresentationDocument())
+ {
+ try
+ {
+ if (sourceNum == 0)
+ CopyPresentationParts(doc, output, images, mediaList);
+ currentMasterPart = AppendSlides(doc, output, source.Start, source.Count, source.KeepMaster, images, currentMasterPart, mediaList);
+ }
+ catch (PresentationBuilderInternalException dbie)
+ {
+ if (dbie.Message.Contains("{0}"))
+ throw new PresentationBuilderException(string.Format(dbie.Message, sourceNum));
+ else
+ throw dbie;
+ }
+ }
+ sourceNum++;
+ }
+ foreach (var part in output.GetAllParts())
+ {
+ if (part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.theme+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" ||
+ part.ContentType == "application/vnd.ms-office.drawingml.diagramDrawing+xml")
+ {
+ XDocument xd = part.GetXDocument();
+ xd.Descendants().Attributes("smtClean").Remove();
+ part.PutXDocument();
+ }
+ else if (part.Annotation<XDocument>() != null)
+ part.PutXDocument();
+ }
+ }
+
+ private static void CopyStartingParts(PresentationDocument sourceDocument, PresentationDocument newDocument)
+ {
+ // A Core File Properties part does not have implicit or explicit relationships to other parts.
+ CoreFilePropertiesPart corePart = sourceDocument.CoreFilePropertiesPart;
+ if (corePart != null && corePart.GetXDocument().Root != null)
+ {
+ newDocument.AddCoreFilePropertiesPart();
+ XDocument newXDoc = newDocument.CoreFilePropertiesPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ XDocument sourceXDoc = corePart.GetXDocument();
+ newXDoc.Add(sourceXDoc.Root);
+ }
+
+ // An application attributes part does not have implicit or explicit relationships to other parts.
+ ExtendedFilePropertiesPart extPart = sourceDocument.ExtendedFilePropertiesPart;
+ if (extPart != null)
+ {
+ OpenXmlPart newPart = newDocument.AddExtendedFilePropertiesPart();
+ XDocument newXDoc = newDocument.ExtendedFilePropertiesPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(extPart.GetXDocument().Root);
+ }
+
+ // An custom file properties part does not have implicit or explicit relationships to other parts.
+ CustomFilePropertiesPart customPart = sourceDocument.CustomFilePropertiesPart;
+ if (customPart != null)
+ {
+ newDocument.AddCustomFilePropertiesPart();
+ XDocument newXDoc = newDocument.CustomFilePropertiesPart.GetXDocument();
+ newXDoc.Declaration.Standalone = "yes";
+ newXDoc.Declaration.Encoding = "UTF-8";
+ newXDoc.Add(customPart.GetXDocument().Root);
+ }
+ }
+
+#if false
+ // TODO need to handle the following
+
+ { P.custShowLst, 80 },
+ { P.photoAlbum, 90 },
+ { P.custDataLst, 100 },
+ { P.kinsoku, 120 },
+ { P.modifyVerifier, 150 },
+#endif
+
+ // Copy handout master, notes master, presentation properties and view properties, if they exist
+ private static void CopyPresentationParts(PresentationDocument sourceDocument, PresentationDocument newDocument, List<ImageData> images, List<MediaData> mediaList)
+ {
+ XDocument newPresentation = newDocument.PresentationPart.GetXDocument();
+
+ // Copy slide and note slide sizes
+ XDocument oldPresentationDoc = sourceDocument.PresentationPart.GetXDocument();
+
+ foreach (var att in oldPresentationDoc.Root.Attributes())
+ {
+ if (!att.IsNamespaceDeclaration && newPresentation.Root.Attribute(att.Name) == null)
+ newPresentation.Root.Add(oldPresentationDoc.Root.Attribute(att.Name));
+ }
+
+ XElement oldElement = oldPresentationDoc.Root.Elements(P.sldSz).FirstOrDefault();
+ if (oldElement != null)
+ newPresentation.Root.Add(oldElement);
+
+ // Copy Font Parts
+ if (oldPresentationDoc.Root.Element(P.embeddedFontLst) != null)
+ {
+ XElement newFontLst = new XElement(P.embeddedFontLst);
+ foreach (var font in oldPresentationDoc.Root.Element(P.embeddedFontLst).Elements(P.embeddedFont))
+ {
+ XElement newRegular = null, newBold = null, newItalic = null, newBoldItalic = null;
+ if (font.Element(P.regular) != null)
+ newRegular = CreatedEmbeddedFontPart(sourceDocument, newDocument, font, P.regular);
+ if (font.Element(P.bold) != null)
+ newBold = CreatedEmbeddedFontPart(sourceDocument, newDocument, font, P.bold);
+ if (font.Element(P.italic) != null)
+ newItalic = CreatedEmbeddedFontPart(sourceDocument, newDocument, font, P.italic);
+ if (font.Element(P.boldItalic) != null)
+ newBoldItalic = CreatedEmbeddedFontPart(sourceDocument, newDocument, font, P.boldItalic);
+ XElement newEmbeddedFont = new XElement(P.embeddedFont,
+ font.Elements(P.font),
+ newRegular,
+ newBold,
+ newItalic,
+ newBoldItalic);
+ newFontLst.Add(newEmbeddedFont);
+ }
+ newPresentation.Root.Add(newFontLst);
+ }
+
+ newPresentation.Root.Add(oldPresentationDoc.Root.Element(P.defaultTextStyle));
+ newPresentation.Root.Add(oldPresentationDoc.Root.Elements(P.extLst));
+
+ //<p:embeddedFont xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main"
+ // xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
+ // <p:font typeface="Perpetua" panose="02020502060401020303" pitchFamily="18" charset="0" />
+ // <p:regular r:id="rId5" />
+ // <p:bold r:id="rId6" />
+ // <p:italic r:id="rId7" />
+ // <p:boldItalic r:id="rId8" />
+ //</p:embeddedFont>
+
+ // Copy Handout Master
+ if (sourceDocument.PresentationPart.HandoutMasterPart != null)
+ {
+ HandoutMasterPart oldMaster = sourceDocument.PresentationPart.HandoutMasterPart;
+ HandoutMasterPart newMaster = newDocument.PresentationPart.AddNewPart<HandoutMasterPart>();
+
+ // Copy theme for master
+ ThemePart newThemePart = newMaster.AddNewPart<ThemePart>();
+ newThemePart.PutXDocument(oldMaster.ThemePart.GetXDocument());
+ CopyRelatedPartsForContentParts(newDocument, oldMaster.ThemePart, newThemePart, new[] { newThemePart.GetXDocument().Root }, images, mediaList);
+
+ // Copy master
+ newMaster.PutXDocument(oldMaster.GetXDocument());
+ AddRelationships(oldMaster, newMaster, new[] { newMaster.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, oldMaster, newMaster, new[] { newMaster.GetXDocument().Root }, images, mediaList);
+
+ newPresentation.Root.Add(
+ new XElement(P.handoutMasterIdLst, new XElement(P.handoutMasterId,
+ new XAttribute(R.id, newDocument.PresentationPart.GetIdOfPart(newMaster)))));
+ }
+
+ // Copy Notes Master
+ CopyNotesMaster(sourceDocument, newDocument, images, mediaList);
+
+ // Copy Presentation Properties
+ if (sourceDocument.PresentationPart.PresentationPropertiesPart != null)
+ {
+ PresentationPropertiesPart newPart = newDocument.PresentationPart.AddNewPart<PresentationPropertiesPart>();
+ XDocument xd1 = sourceDocument.PresentationPart.PresentationPropertiesPart.GetXDocument();
+ xd1.Descendants(P.custShow).Remove();
+ newPart.PutXDocument(xd1);
+ }
+
+ // Copy View Properties
+ if (sourceDocument.PresentationPart.ViewPropertiesPart != null)
+ {
+ ViewPropertiesPart newPart = newDocument.PresentationPart.AddNewPart<ViewPropertiesPart>();
+ XDocument xd = sourceDocument.PresentationPart.ViewPropertiesPart.GetXDocument();
+ xd.Descendants(P.outlineViewPr).Elements(P.sldLst).Remove();
+ newPart.PutXDocument(xd);
+ }
+
+ foreach (var legacyDocTextInfo in sourceDocument.PresentationPart.Parts.Where(p => p.OpenXmlPart.RelationshipType == "http://schemas.microsoft.com/office/2006/relationships/legacyDocTextInfo"))
+ {
+ LegacyDiagramTextInfoPart newPart = newDocument.PresentationPart.AddNewPart<LegacyDiagramTextInfoPart>();
+ newPart.FeedData(legacyDocTextInfo.OpenXmlPart.GetStream());
+ }
+
+ var listOfRootChildren = newPresentation.Root.Elements().ToList();
+ foreach (var rc in listOfRootChildren)
+ rc.Remove();
+ newPresentation.Root.Add(
+ listOfRootChildren.OrderBy(e =>
+ {
+ if (Order_presentation.ContainsKey(e.Name))
+ return Order_presentation[e.Name];
+ return 999;
+ }));
+ }
+
+ private static Dictionary<XName, int> Order_presentation = new Dictionary<XName, int>
+ {
+ { P.sldMasterIdLst, 10 },
+ { P.notesMasterIdLst, 20 },
+ { P.handoutMasterIdLst, 30 },
+ { P.sldIdLst, 40 },
+ { P.sldSz, 50 },
+ { P.notesSz, 60 },
+ { P.embeddedFontLst, 70 },
+ { P.custShowLst, 80 },
+ { P.photoAlbum, 90 },
+ { P.custDataLst, 100 },
+ { P.kinsoku, 120 },
+ { P.defaultTextStyle, 130 },
+ { P.modifyVerifier, 150 },
+ { P.extLst, 160 },
+ };
+
+
+ private static XElement CreatedEmbeddedFontPart(PresentationDocument sourceDocument, PresentationDocument newDocument, XElement font, XName fontXName)
+ {
+ XElement newRegular;
+ FontPart oldFontPart = (FontPart)sourceDocument.PresentationPart.GetPartById((string)font.Element(fontXName).Attributes(R.id).FirstOrDefault());
+ FontPartType fpt;
+ if (oldFontPart.ContentType == "application/x-fontdata")
+ fpt = FontPartType.FontData;
+ else if (oldFontPart.ContentType == "application/x-font-ttf")
+ fpt = FontPartType.FontTtf;
+ else
+ fpt = FontPartType.FontOdttf;
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ var newFontPart = newDocument.PresentationPart.AddFontPart(fpt, newId);
+ newFontPart.FeedData(oldFontPart.GetStream());
+ newRegular = new XElement(fontXName,
+ new XAttribute(R.id, newId));
+ return newRegular;
+ }
+
+ private static SlideMasterPart AppendSlides(PresentationDocument sourceDocument, PresentationDocument newDocument,
+ int start, int count, bool keepMaster, List<ImageData> images, SlideMasterPart currentMasterPart, List<MediaData> mediaList)
+ {
+ XDocument newPresentation = newDocument.PresentationPart.GetXDocument();
+ if (newPresentation.Root.Element(P.sldIdLst) == null)
+ newPresentation.Root.Add(new XElement(P.sldIdLst));
+ uint newID = 256;
+ var ids = newPresentation.Root.Descendants(P.sldId).Select(f => (uint)f.Attribute(NoNamespace.id));
+ if (ids.Any())
+ newID = ids.Max() + 1;
+ var slideList = sourceDocument.PresentationPart.GetXDocument().Root.Descendants(P.sldId);
+ if (slideList.Count() == 0 && (currentMasterPart == null || keepMaster))
+ {
+ var slideMasterPart = sourceDocument.PresentationPart.SlideMasterParts.FirstOrDefault();
+ if (slideMasterPart != null)
+ currentMasterPart = CopyMasterSlide(sourceDocument, slideMasterPart, newDocument, newPresentation, images, mediaList);
+ return currentMasterPart;
+ }
+ while (count > 0 && start < slideList.Count())
+ {
+ SlidePart slide = (SlidePart)sourceDocument.PresentationPart.GetPartById(slideList.ElementAt(start).Attribute(R.id).Value);
+ if (currentMasterPart == null || keepMaster)
+ currentMasterPart = CopyMasterSlide(sourceDocument, slide.SlideLayoutPart.SlideMasterPart, newDocument, newPresentation, images, mediaList);
+ SlidePart newSlide = newDocument.PresentationPart.AddNewPart<SlidePart>();
+ newSlide.PutXDocument(slide.GetXDocument());
+ AddRelationships(slide, newSlide, new[] { newSlide.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, slide, newSlide, new[] { newSlide.GetXDocument().Root }, images, mediaList);
+ CopyTableStyles(sourceDocument, newDocument, slide, newSlide);
+ if (slide.NotesSlidePart != null)
+ {
+ if (newDocument.PresentationPart.NotesMasterPart == null)
+ CopyNotesMaster(sourceDocument, newDocument, images, mediaList);
+ NotesSlidePart newPart = newSlide.AddNewPart<NotesSlidePart>();
+ newPart.PutXDocument(slide.NotesSlidePart.GetXDocument());
+ newPart.AddPart(newSlide);
+ newPart.AddPart(newDocument.PresentationPart.NotesMasterPart);
+ AddRelationships(slide.NotesSlidePart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, slide.NotesSlidePart, newPart, new[] { newPart.GetXDocument().Root }, images, mediaList);
+ }
+
+ string layoutName = slide.SlideLayoutPart.GetXDocument().Root.Element(P.cSld).Attribute(NoNamespace.name).Value;
+ foreach (SlideLayoutPart layoutPart in currentMasterPart.SlideLayoutParts)
+ if (layoutPart.GetXDocument().Root.Element(P.cSld).Attribute(NoNamespace.name).Value == layoutName)
+ {
+ newSlide.AddPart(layoutPart);
+ break;
+ }
+ if (newSlide.SlideLayoutPart == null)
+ newSlide.AddPart(currentMasterPart.SlideLayoutParts.First()); // Cannot find matching layout part
+
+ if (slide.SlideCommentsPart != null)
+ CopyComments(sourceDocument, newDocument, slide, newSlide);
+
+ newPresentation.Root.Element(P.sldIdLst).Add(new XElement(P.sldId,
+ new XAttribute(NoNamespace.id, newID.ToString()),
+ new XAttribute(R.id, newDocument.PresentationPart.GetIdOfPart(newSlide))));
+ newID++;
+ start++;
+ count--;
+ }
+ return currentMasterPart;
+ }
+
+ private static SlideMasterPart CopyMasterSlide(PresentationDocument sourceDocument, SlideMasterPart sourceMasterPart,
+ PresentationDocument newDocument, XDocument newPresentation, List<ImageData> images, List<MediaData> mediaList)
+ {
+ // Search for existing master slide with same theme name
+ XDocument oldTheme = sourceMasterPart.ThemePart.GetXDocument();
+ String themeName = oldTheme.Root.Attribute(NoNamespace.name).Value;
+ foreach (SlideMasterPart master in newDocument.PresentationPart.GetPartsOfType<SlideMasterPart>())
+ {
+ XDocument themeDoc = master.ThemePart.GetXDocument();
+ if (themeDoc.Root.Attribute(NoNamespace.name).Value == themeName)
+ return master;
+ }
+
+ SlideMasterPart newMaster = newDocument.PresentationPart.AddNewPart<SlideMasterPart>();
+ XDocument sourceMaster = sourceMasterPart.GetXDocument();
+
+ // Add to presentation slide master list, need newID for layout IDs also
+ uint newID = 2147483648;
+ var ids = newPresentation.Root.Descendants(P.sldMasterId).Select(f => (uint)f.Attribute(NoNamespace.id));
+ if (ids.Any())
+ {
+ newID = ids.Max();
+ XElement maxMaster = newPresentation.Root.Descendants(P.sldMasterId).Where(f => (uint)f.Attribute(NoNamespace.id) == newID).FirstOrDefault();
+ SlideMasterPart maxMasterPart = (SlideMasterPart)newDocument.PresentationPart.GetPartById(maxMaster.Attribute(R.id).Value);
+ newID += (uint)maxMasterPart.GetXDocument().Root.Descendants(P.sldLayoutId).Count() + 1;
+ }
+ newPresentation.Root.Element(P.sldMasterIdLst).Add(new XElement(P.sldMasterId,
+ new XAttribute(NoNamespace.id, newID.ToString()),
+ new XAttribute(R.id, newDocument.PresentationPart.GetIdOfPart(newMaster))));
+ newID++;
+
+ ThemePart newThemePart = newMaster.AddNewPart<ThemePart>();
+ if (newDocument.PresentationPart.ThemePart == null)
+ newThemePart = newDocument.PresentationPart.AddPart(newThemePart);
+ newThemePart.PutXDocument(oldTheme);
+ CopyRelatedPartsForContentParts(newDocument, sourceMasterPart.ThemePart, newThemePart, new[] { newThemePart.GetXDocument().Root }, images, mediaList);
+ foreach (SlideLayoutPart layoutPart in sourceMasterPart.SlideLayoutParts)
+ {
+ SlideLayoutPart newLayout = newMaster.AddNewPart<SlideLayoutPart>();
+ newLayout.PutXDocument(layoutPart.GetXDocument());
+ AddRelationships(layoutPart, newLayout, new[] { newLayout.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, layoutPart, newLayout, new[] { newLayout.GetXDocument().Root }, images, mediaList);
+ newLayout.AddPart(newMaster);
+ string resID = sourceMasterPart.GetIdOfPart(layoutPart);
+ XElement entry = sourceMaster.Root.Descendants(P.sldLayoutId).Where(f => f.Attribute(R.id).Value == resID).FirstOrDefault();
+ entry.Attribute(R.id).SetValue(newMaster.GetIdOfPart(newLayout));
+ entry.SetAttributeValue(NoNamespace.id, newID.ToString());
+ newID++;
+ }
+ newMaster.PutXDocument(sourceMaster);
+ AddRelationships(sourceMasterPart, newMaster, new[] { newMaster.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, sourceMasterPart, newMaster, new[] { newMaster.GetXDocument().Root }, images, mediaList);
+
+ return newMaster;
+ }
+
+ // Copies notes master and notesSz element from presentation
+ private static void CopyNotesMaster(PresentationDocument sourceDocument, PresentationDocument newDocument, List<ImageData> images, List<MediaData> mediaList)
+ {
+ // Copy notesSz element from presentation
+ XDocument newPresentation = newDocument.PresentationPart.GetXDocument();
+ XDocument oldPresentationDoc = sourceDocument.PresentationPart.GetXDocument();
+ XElement oldElement = oldPresentationDoc.Root.Element(P.notesSz);
+ newPresentation.Root.Element(P.notesSz).ReplaceWith(oldElement);
+
+ // Copy Notes Master
+ if (sourceDocument.PresentationPart.NotesMasterPart != null)
+ {
+ NotesMasterPart oldMaster = sourceDocument.PresentationPart.NotesMasterPart;
+ NotesMasterPart newMaster = newDocument.PresentationPart.AddNewPart<NotesMasterPart>();
+
+ // Copy theme for master
+ if (oldMaster.ThemePart != null)
+ {
+ ThemePart newThemePart = newMaster.AddNewPart<ThemePart>();
+ newThemePart.PutXDocument(oldMaster.ThemePart.GetXDocument());
+ CopyRelatedPartsForContentParts(newDocument, oldMaster.ThemePart, newThemePart, new[] { newThemePart.GetXDocument().Root }, images, mediaList);
+ }
+
+ // Copy master
+ newMaster.PutXDocument(oldMaster.GetXDocument());
+ AddRelationships(oldMaster, newMaster, new[] { newMaster.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, oldMaster, newMaster, new[] { newMaster.GetXDocument().Root }, images, mediaList);
+
+ newPresentation.Root.Add(
+ new XElement(P.notesMasterIdLst, new XElement(P.notesMasterId,
+ new XAttribute(R.id, newDocument.PresentationPart.GetIdOfPart(newMaster)))));
+ }
+ }
+
+ private static void CopyComments(PresentationDocument oldDocument, PresentationDocument newDocument, SlidePart oldSlide, SlidePart newSlide)
+ {
+ newSlide.AddNewPart<SlideCommentsPart>();
+ newSlide.SlideCommentsPart.PutXDocument(oldSlide.SlideCommentsPart.GetXDocument());
+ XDocument newSlideComments = newSlide.SlideCommentsPart.GetXDocument();
+ XDocument oldAuthors = oldDocument.PresentationPart.CommentAuthorsPart.GetXDocument();
+ foreach (XElement comment in newSlideComments.Root.Elements(P.cm))
+ {
+ XElement newAuthor = FindCommentsAuthor(newDocument, comment, oldAuthors);
+ // Update last index value for new comment
+ comment.Attribute(NoNamespace.authorId).SetValue(newAuthor.Attribute(NoNamespace.id).Value);
+ uint lastIndex = Convert.ToUInt32(newAuthor.Attribute(NoNamespace.lastIdx).Value);
+ comment.Attribute(NoNamespace.idx).SetValue(lastIndex.ToString());
+ newAuthor.Attribute(NoNamespace.lastIdx).SetValue(Convert.ToString(lastIndex + 1));
+ }
+ }
+
+ private static XElement FindCommentsAuthor(PresentationDocument newDocument, XElement comment, XDocument oldAuthors)
+ {
+ XElement oldAuthor = oldAuthors.Root.Elements(P.cmAuthor).Where(
+ f => f.Attribute(NoNamespace.id).Value == comment.Attribute(NoNamespace.authorId).Value).FirstOrDefault();
+ XElement newAuthor = null;
+ if (newDocument.PresentationPart.CommentAuthorsPart == null)
+ {
+ newDocument.PresentationPart.AddNewPart<CommentAuthorsPart>();
+ newDocument.PresentationPart.CommentAuthorsPart.PutXDocument(new XDocument(new XElement(P.cmAuthorLst,
+ new XAttribute(XNamespace.Xmlns + "a", A.a),
+ new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XAttribute(XNamespace.Xmlns + "p", P.p))));
+ }
+ XDocument authors = newDocument.PresentationPart.CommentAuthorsPart.GetXDocument();
+ newAuthor = authors.Root.Elements(P.cmAuthor).Where(
+ f => f.Attribute(NoNamespace.initials).Value == oldAuthor.Attribute(NoNamespace.initials).Value).FirstOrDefault();
+ if (newAuthor == null)
+ {
+ uint newID = 0;
+ var ids = authors.Root.Descendants(P.cmAuthor).Select(f => (uint)f.Attribute(NoNamespace.id));
+ if (ids.Any())
+ newID = ids.Max() + 1;
+
+ newAuthor = new XElement(P.cmAuthor, new XAttribute(NoNamespace.id, newID.ToString()),
+ new XAttribute(NoNamespace.name, oldAuthor.Attribute(NoNamespace.name).Value),
+ new XAttribute(NoNamespace.initials, oldAuthor.Attribute(NoNamespace.initials).Value),
+ new XAttribute(NoNamespace.lastIdx, "1"), new XAttribute(NoNamespace.clrIdx, newID.ToString()));
+ authors.Root.Add(newAuthor);
+ }
+
+ return newAuthor;
+ }
+
+ private static void CopyTableStyles(PresentationDocument oldDocument, PresentationDocument newDocument, OpenXmlPart oldContentPart, OpenXmlPart newContentPart)
+ {
+ foreach (XElement table in newContentPart.GetXDocument().Descendants(A.tableStyleId))
+ {
+ string styleId = table.Value;
+ if (string.IsNullOrEmpty(styleId))
+ continue;
+
+ // Find old style
+ if (oldDocument.PresentationPart.TableStylesPart == null)
+ continue;
+ XDocument oldTableStyles = oldDocument.PresentationPart.TableStylesPart.GetXDocument();
+ XElement oldStyle = oldTableStyles.Root.Elements(A.tblStyle).Where(f => f.Attribute(NoNamespace.styleId).Value == styleId).FirstOrDefault();
+ if (oldStyle == null)
+ continue;
+
+ // Create new TableStylesPart, if needed
+ XDocument tableStyles = null;
+ if (newDocument.PresentationPart.TableStylesPart == null)
+ {
+ TableStylesPart newStylesPart = newDocument.PresentationPart.AddNewPart<TableStylesPart>();
+ tableStyles = new XDocument(new XElement(A.tblStyleLst,
+ new XAttribute(XNamespace.Xmlns + "a", A.a),
+ new XAttribute(NoNamespace.def, styleId)));
+ newStylesPart.PutXDocument(tableStyles);
+ }
+ else
+ tableStyles = newDocument.PresentationPart.TableStylesPart.GetXDocument();
+
+ // Search new TableStylesPart to see if it contains the ID
+ if (tableStyles.Root.Elements(A.tblStyle).Where(f => f.Attribute(NoNamespace.styleId).Value == styleId).FirstOrDefault() != null)
+ continue;
+
+ // Copy style to new part
+ tableStyles.Root.Add(oldStyle);
+ }
+
+ }
+
+ private static void CopyRelatedPartsForContentParts(PresentationDocument newDocument, OpenXmlPart oldContentPart, OpenXmlPart newContentPart,
+ IEnumerable<XElement> newContent, List<ImageData> images, List<MediaData> mediaList)
+ {
+ var relevantElements = newContent.DescendantsAndSelf()
+ .Where(d => d.Name == VML.imagedata || d.Name == VML.fill || d.Name == VML.stroke || d.Name == A.blip)
+ .ToList();
+ foreach (XElement imageReference in relevantElements)
+ {
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.embed, images);
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.pict, images);
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.id, images);
+ CopyRelatedImage(oldContentPart, newContentPart, imageReference, O.relid, images);
+ }
+
+ relevantElements = newContent.DescendantsAndSelf()
+ .Where(d => d.Name == A.videoFile || d.Name == A.quickTimeFile)
+ .ToList();
+ foreach (XElement imageReference in relevantElements)
+ {
+ CopyRelatedMedia(oldContentPart, newContentPart, imageReference, R.link, mediaList, "video");
+ }
+
+ relevantElements = newContent.DescendantsAndSelf()
+ .Where(d => d.Name == P14.media || d.Name == PAV.srcMedia)
+ .ToList();
+ foreach (XElement imageReference in relevantElements)
+ {
+ CopyRelatedMedia(oldContentPart, newContentPart, imageReference, R.embed, mediaList, "media");
+ CopyRelatedMediaExternalRelationship(oldContentPart, newContentPart, imageReference, R.link, "media");
+ }
+
+ foreach (XElement extendedReference in newContent.DescendantsAndSelf(A14.imgLayer))
+ {
+ CopyExtendedPart(oldContentPart, newContentPart, extendedReference, R.embed);
+ }
+
+ foreach (XElement contentPartReference in newContent.DescendantsAndSelf(P.contentPart))
+ {
+ CopyInkPart(oldContentPart, newContentPart, contentPartReference, R.id);
+ }
+
+ foreach (XElement contentPartReference in newContent.DescendantsAndSelf(P.control))
+ {
+ CopyActiveXPart(oldContentPart, newContentPart, contentPartReference, R.id);
+ }
+
+ foreach (XElement contentPartReference in newContent.DescendantsAndSelf(Plegacy.textdata))
+ {
+ CopyLegacyDiagramText(oldContentPart, newContentPart, contentPartReference, "id");
+ }
+
+ foreach (XElement diagramReference in newContent.DescendantsAndSelf().Where(d => d.Name == DGM.relIds || d.Name == A.relIds))
+ {
+ // dm attribute
+ string relId = diagramReference.Attribute(R.dm).Value;
+ var tempPartIdPair = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair != null)
+ continue;
+
+ ExternalRelationship tempEr = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr != null)
+ continue;
+
+ OpenXmlPart oldPart = oldContentPart.GetPartById(relId);
+ OpenXmlPart newPart = newContentPart.AddNewPart<DiagramDataPart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.dm).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, oldPart, newPart, new[] { newPart.GetXDocument().Root }, images, mediaList);
+
+ // lo attribute
+ relId = diagramReference.Attribute(R.lo).Value;
+ var tempPartIdPair2 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair2 != null)
+ continue;
+
+ ExternalRelationship tempEr2 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr2 != null)
+ continue;
+
+ oldPart = oldContentPart.GetPartById(relId);
+ newPart = newContentPart.AddNewPart<DiagramLayoutDefinitionPart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.lo).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, oldPart, newPart, new[] { newPart.GetXDocument().Root }, images, mediaList);
+
+ // qs attribute
+ relId = diagramReference.Attribute(R.qs).Value;
+ var tempPartIdPair3 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair3 != null)
+ continue;
+
+ ExternalRelationship tempEr3 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr3 != null)
+ continue;
+
+ oldPart = oldContentPart.GetPartById(relId);
+ newPart = newContentPart.AddNewPart<DiagramStylePart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.qs).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, oldPart, newPart, new[] { newPart.GetXDocument().Root }, images, mediaList);
+
+ // cs attribute
+ relId = diagramReference.Attribute(R.cs).Value;
+ var tempPartIdPair4 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair4 != null)
+ continue;
+
+ ExternalRelationship tempEr4 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr4 != null)
+ continue;
+
+ oldPart = oldContentPart.GetPartById(relId);
+ newPart = newContentPart.AddNewPart<DiagramColorsPart>();
+ newPart.GetXDocument().Add(oldPart.GetXDocument().Root);
+ diagramReference.Attribute(R.cs).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, oldPart, newPart, new[] { newPart.GetXDocument().Root }, images, mediaList);
+ }
+
+ foreach (XElement oleReference in newContent.DescendantsAndSelf().Where(d => d.Name == P.oleObj || d.Name == P.externalData))
+ {
+ string relId = oleReference.Attribute(R.id).Value;
+
+ // First look to see if this relId has already been added to the new document.
+ // This is necessary for those parts that get processed with both old and new ids, such as the comments
+ // part. This is not necessary for parts such as the main document part, but this code won't malfunction
+ // in that case.
+ var tempPartIdPair5 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair5 != null)
+ continue;
+
+ ExternalRelationship tempEr5 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr5 != null)
+ continue;
+
+ var oldPartIdPair = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair != null)
+ {
+ OpenXmlPart oldPart = oldPartIdPair.OpenXmlPart;
+ OpenXmlPart newPart = null;
+ if (oldPart is EmbeddedObjectPart)
+ {
+ if (newContentPart is DialogsheetPart)
+ newPart = ((DialogsheetPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ else if (newContentPart is HandoutMasterPart)
+ newPart = ((HandoutMasterPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ else if (newContentPart is NotesMasterPart)
+ newPart = ((NotesMasterPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ else if (newContentPart is NotesSlidePart)
+ newPart = ((NotesSlidePart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ else if (newContentPart is SlideLayoutPart)
+ newPart = ((SlideLayoutPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ else if (newContentPart is SlideMasterPart)
+ newPart = ((SlideMasterPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ else if (newContentPart is SlidePart)
+ newPart = ((SlidePart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType);
+ }
+ else if (oldPart is EmbeddedPackagePart)
+ {
+ if (newContentPart is ChartPart)
+ newPart = ((ChartPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ else if (newContentPart is HandoutMasterPart)
+ newPart = ((HandoutMasterPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ else if (newContentPart is NotesMasterPart)
+ newPart = ((NotesMasterPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ else if (newContentPart is NotesSlidePart)
+ newPart = ((NotesSlidePart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ else if (newContentPart is SlideLayoutPart)
+ newPart = ((SlideLayoutPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ else if (newContentPart is SlideMasterPart)
+ newPart = ((SlideMasterPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ else if (newContentPart is SlidePart)
+ newPart = ((SlidePart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType);
+ }
+ using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0)
+ newObject.Write(buffer, 0, byteCount);
+ }
+ oleReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ }
+ else
+ {
+ ExternalRelationship er = oldContentPart.GetExternalRelationship(relId);
+ ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri);
+ oleReference.Attribute(R.id).Value = newEr.Id;
+ }
+ }
+
+ foreach (XElement chartReference in newContent.DescendantsAndSelf(C.chart))
+ {
+ string relId = (string)chartReference.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+
+ var tempPartIdPair6 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair6 != null)
+ continue;
+
+ ExternalRelationship tempEr6 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr6 != null)
+ continue;
+
+ var oldPartIdPair2 = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair2 != null)
+ {
+ ChartPart oldPart = oldPartIdPair2.OpenXmlPart as ChartPart;
+ if (oldPart != null)
+ {
+ XDocument oldChart = oldPart.GetXDocument();
+ ChartPart newPart = newContentPart.AddNewPart<ChartPart>();
+ XDocument newChart = newPart.GetXDocument();
+ newChart.Add(oldChart.Root);
+ chartReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ CopyChartObjects(oldPart, newPart);
+ CopyRelatedPartsForContentParts(newDocument, oldPart, newPart, new[] { newChart.Root }, images, mediaList);
+ }
+ }
+ }
+
+ foreach (XElement userShape in newContent.DescendantsAndSelf(C.userShapes))
+ {
+ string relId = (string)userShape.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+
+ var tempPartIdPair7 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair7 != null)
+ continue;
+
+ ExternalRelationship tempEr7 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr7 != null)
+ continue;
+
+ var oldPartIdPair3 = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair3 != null)
+ {
+ ChartDrawingPart oldPart = oldPartIdPair3.OpenXmlPart as ChartDrawingPart;
+ if (oldPart != null)
+ {
+ XDocument oldXDoc = oldPart.GetXDocument();
+ ChartDrawingPart newPart = newContentPart.AddNewPart<ChartDrawingPart>();
+ XDocument newXDoc = newPart.GetXDocument();
+ newXDoc.Add(oldXDoc.Root);
+ userShape.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ AddRelationships(oldPart, newPart, newContent);
+ CopyRelatedPartsForContentParts(newDocument, oldPart, newPart, new[] { newXDoc.Root }, images, mediaList);
+ }
+ }
+ }
+
+ foreach (XElement tags in newContent.DescendantsAndSelf(P.tags))
+ {
+ string relId = (string)tags.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+
+ var tempPartIdPair8 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair8 != null)
+ continue;
+
+ ExternalRelationship tempEr8 = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr8 != null)
+ continue;
+
+ var oldPartIdPair4 = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair4 != null)
+ {
+ UserDefinedTagsPart oldPart = oldPartIdPair4.OpenXmlPart as UserDefinedTagsPart;
+ if (oldPart != null)
+ {
+ XDocument oldXDoc = oldPart.GetXDocument();
+ UserDefinedTagsPart newPart = newContentPart.AddNewPart<UserDefinedTagsPart>();
+ XDocument newXDoc = newPart.GetXDocument();
+ newXDoc.Add(oldXDoc.Root);
+ tags.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart);
+ }
+ }
+ }
+
+ foreach (XElement custData in newContent.DescendantsAndSelf(P.custData))
+ {
+ string relId = (string)custData.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+
+ var tempPartIdPair9 = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair9 != null)
+ continue;
+
+ var oldPartIdPair9 = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair9 != null)
+ {
+ CustomXmlPart newPart = newDocument.PresentationPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
+ newPart.FeedData(oldPartIdPair9.OpenXmlPart.GetStream());
+ foreach (var itemProps in oldPartIdPair9.OpenXmlPart.Parts.Where(p => p.OpenXmlPart.ContentType == "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"))
+ {
+ var newId2 = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ CustomXmlPropertiesPart cxpp = newPart.AddNewPart<CustomXmlPropertiesPart>("application/vnd.openxmlformats-officedocument.customXmlProperties+xml", newId2);
+ cxpp.FeedData(itemProps.OpenXmlPart.GetStream());
+ }
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ newContentPart.CreateRelationshipToPart(newPart, newId);
+ custData.Attribute(R.id).Value = newId;
+ }
+ }
+
+ foreach (XElement soundReference in newContent.DescendantsAndSelf().Where(d => d.Name == A.audioFile))
+ CopyRelatedSound(newDocument, oldContentPart, newContentPart, soundReference, R.link);
+
+ if ((oldContentPart is ChartsheetPart && newContentPart is ChartsheetPart) ||
+ (oldContentPart is DialogsheetPart && newContentPart is DialogsheetPart) ||
+ (oldContentPart is HandoutMasterPart && newContentPart is HandoutMasterPart) ||
+ (oldContentPart is InternationalMacroSheetPart && newContentPart is InternationalMacroSheetPart) ||
+ (oldContentPart is MacroSheetPart && newContentPart is MacroSheetPart) ||
+ (oldContentPart is NotesMasterPart && newContentPart is NotesMasterPart) ||
+ (oldContentPart is NotesSlidePart && newContentPart is NotesSlidePart) ||
+ (oldContentPart is SlideLayoutPart && newContentPart is SlideLayoutPart) ||
+ (oldContentPart is SlideMasterPart && newContentPart is SlideMasterPart) ||
+ (oldContentPart is SlidePart && newContentPart is SlidePart) ||
+ (oldContentPart is WorksheetPart && newContentPart is WorksheetPart))
+ {
+ foreach (XElement soundReference in newContent.DescendantsAndSelf().Where(d => d.Name == P.snd || d.Name == P.sndTgt || d.Name == A.wavAudioFile || d.Name == A.snd || d.Name == PAV.srcMedia))
+ CopyRelatedSound(newDocument, oldContentPart, newContentPart, soundReference, R.embed);
+
+ IEnumerable<VmlDrawingPart> vmlDrawingParts = null;
+ if (oldContentPart is ChartsheetPart)
+ vmlDrawingParts = ((ChartsheetPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is DialogsheetPart)
+ vmlDrawingParts = ((DialogsheetPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is HandoutMasterPart)
+ vmlDrawingParts = ((HandoutMasterPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is InternationalMacroSheetPart)
+ vmlDrawingParts = ((InternationalMacroSheetPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is MacroSheetPart)
+ vmlDrawingParts = ((MacroSheetPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is NotesMasterPart)
+ vmlDrawingParts = ((NotesMasterPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is NotesSlidePart)
+ vmlDrawingParts = ((NotesSlidePart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is SlideLayoutPart)
+ vmlDrawingParts = ((SlideLayoutPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is SlideMasterPart)
+ vmlDrawingParts = ((SlideMasterPart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is SlidePart)
+ vmlDrawingParts = ((SlidePart)oldContentPart).VmlDrawingParts;
+ if (oldContentPart is WorksheetPart)
+ vmlDrawingParts = ((WorksheetPart)oldContentPart).VmlDrawingParts;
+
+ if (vmlDrawingParts != null)
+ {
+ // Transitional: Copy VML Drawing parts, implicit relationship
+ foreach (VmlDrawingPart vmlPart in vmlDrawingParts)
+ {
+ VmlDrawingPart newVmlPart = null;
+ if (newContentPart is ChartsheetPart)
+ newVmlPart = ((ChartsheetPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is DialogsheetPart)
+ newVmlPart = ((DialogsheetPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is HandoutMasterPart)
+ newVmlPart = ((HandoutMasterPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is InternationalMacroSheetPart)
+ newVmlPart = ((InternationalMacroSheetPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is MacroSheetPart)
+ newVmlPart = ((MacroSheetPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is NotesMasterPart)
+ newVmlPart = ((NotesMasterPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is NotesSlidePart)
+ newVmlPart = ((NotesSlidePart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is SlideLayoutPart)
+ newVmlPart = ((SlideLayoutPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is SlideMasterPart)
+ newVmlPart = ((SlideMasterPart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is SlidePart)
+ newVmlPart = ((SlidePart)newContentPart).AddNewPart<VmlDrawingPart>();
+ if (newContentPart is WorksheetPart)
+ newVmlPart = ((WorksheetPart)newContentPart).AddNewPart<VmlDrawingPart>();
+
+ XDocument xd = vmlPart.GetXDocument();
+ foreach (var item in xd.Descendants(O.ink))
+ {
+ if (item.Attribute("i") != null)
+ {
+ var i = item.Attribute("i").Value;
+ i = i.Replace(" ", "\r\n");
+ item.Attribute("i").Value = i;
+ }
+ }
+ newVmlPart.PutXDocument(xd);
+ AddRelationships(vmlPart, newVmlPart, new[] { newVmlPart.GetXDocument().Root });
+ CopyRelatedPartsForContentParts(newDocument, vmlPart, newVmlPart, new[] { newVmlPart.GetXDocument().Root }, images, mediaList);
+ }
+ }
+ }
+ }
+
+ private static void CopyChartObjects(ChartPart oldChart, ChartPart newChart)
+ {
+ foreach (XElement dataReference in newChart.GetXDocument().Descendants(C.externalData))
+ {
+ string relId = dataReference.Attribute(R.id).Value;
+
+ var oldPartIdPair = oldChart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair != null)
+ {
+ EmbeddedPackagePart oldPart = oldPartIdPair.OpenXmlPart as EmbeddedPackagePart;
+ if (oldPart != null)
+ {
+ EmbeddedPackagePart newPart = newChart.AddEmbeddedPackagePart(oldPart.ContentType);
+ using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0)
+ newObject.Write(buffer, 0, byteCount);
+ }
+ dataReference.Attribute(R.id).Value = newChart.GetIdOfPart(newPart);
+ continue;
+ }
+ EmbeddedObjectPart oldEmbeddedObjectPart = oldPartIdPair.OpenXmlPart as EmbeddedObjectPart;
+ if (oldEmbeddedObjectPart != null)
+ {
+ EmbeddedPackagePart newPart = newChart.AddEmbeddedPackagePart(oldEmbeddedObjectPart.ContentType);
+ using (Stream oldObject = oldEmbeddedObjectPart.GetStream(FileMode.Open, FileAccess.Read))
+ using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ {
+ int byteCount;
+ byte[] buffer = new byte[65536];
+ while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0)
+ newObject.Write(buffer, 0, byteCount);
+ }
+
+ var rId = newChart.GetIdOfPart(newPart);
+ dataReference.Attribute(R.id).Value = rId;
+
+ // following is a hack to fix the package because the Open XML SDK does not let us create
+ // a relationship from a chart with the oleObject relationship type.
+
+ var pkg = newChart.OpenXmlPackage.Package;
+ var fromPart = pkg.GetParts().FirstOrDefault(p => p.Uri == newChart.Uri);
+ var rel = fromPart.GetRelationships().FirstOrDefault(p => p.Id == rId);
+ var targetUri = rel.TargetUri;
+
+ fromPart.DeleteRelationship(rId);
+ fromPart.CreateRelationship(targetUri, System.IO.Packaging.TargetMode.Internal,
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships/oleObject", rId);
+
+ continue;
+ }
+ }
+ else
+ {
+ ExternalRelationship oldRelationship = oldChart.GetExternalRelationship(relId);
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ throw new PresentationBuilderInternalException("Internal Error 0007");
+ newChart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ dataReference.Attribute(R.id).Value = newRid;
+ }
+ }
+ }
+
+ private static Dictionary<XName, XName[]> RelationshipMarkup = null;
+
+ private static void UpdateContent(IEnumerable<XElement> newContent, XName elementToModify, string oldRid, string newRid)
+ {
+ foreach (var attributeName in RelationshipMarkup[elementToModify])
+ {
+ var elementsToUpdate = newContent
+ .Descendants(elementToModify)
+ .Where(e => (string)e.Attribute(attributeName) == oldRid);
+ foreach (var element in elementsToUpdate)
+ element.Attribute(attributeName).Value = newRid;
+ }
+ }
+
+ private static void RemoveContent(IEnumerable<XElement> newContent, XName elementToModify, string oldRid)
+ {
+ foreach (var attributeName in RelationshipMarkup[elementToModify])
+ {
+ newContent
+ .Descendants(elementToModify)
+ .Where(e => (string)e.Attribute(attributeName) == oldRid).Remove();
+ }
+ }
+
+ private static void AddRelationships(OpenXmlPart oldPart, OpenXmlPart newPart, IEnumerable<XElement> newContent)
+ {
+ var relevantElements = newContent.DescendantsAndSelf()
+ .Where(d => RelationshipMarkup.ContainsKey(d.Name) &&
+ d.Attributes().Any(a => RelationshipMarkup[d.Name].Contains(a.Name)))
+ .ToList();
+ foreach (var e in relevantElements)
+ {
+ if (e.Name == A.hlinkClick || e.Name == A.hlinkHover || e.Name == A.hlinkMouseOver)
+ {
+ string relId = (string)e.Attribute(R.id);
+ if (string.IsNullOrEmpty(relId))
+ {
+ // handle the following:
+ //<a:hlinkClick r:id=""
+ // action="ppaction://customshow?id=0" />
+ var action = (string)e.Attribute("action");
+ if (action != null)
+ {
+ if (action.Contains("customshow"))
+ e.Attribute("action").Remove();
+ }
+ continue;
+ }
+ var tempHyperlink = newPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempHyperlink != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldHyperlink = oldPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldHyperlink == null) {
+ //TODO Issue with reference to another part: var temp = oldPart.GetPartById(relId);
+ RemoveContent(newContent, e.Name, relId);
+ continue;
+ }
+ newPart.AddHyperlinkRelationship(oldHyperlink.Uri, oldHyperlink.IsExternal, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ if (e.Name == VML.imagedata)
+ {
+ string relId = (string)e.Attribute(R.href);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempExternalRelationship != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ throw new PresentationBuilderInternalException("Internal Error 0006");
+ newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ if (e.Name == A.blip || e.Name == A14.imgLayer || e.Name == A.audioFile || e.Name == A.videoFile || e.Name == A.quickTimeFile)
+ {
+ string relId = (string)e.Attribute(R.link);
+ if (string.IsNullOrEmpty(relId))
+ continue;
+ var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (tempExternalRelationship != null)
+ continue;
+ Guid g = Guid.NewGuid();
+ string newRid = "R" + g.ToString().Replace("-", "");
+ var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId);
+ if (oldRel == null)
+ continue;
+ newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid);
+ UpdateContent(newContent, e.Name, relId, newRid);
+ }
+ }
+ }
+
+ private static void CopyRelatedImage(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement imageReference, XName attributeName,
+ List<ImageData> images)
+ {
+ string relId = (string)imageReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ // First look to see if this relId has already been added to the new document.
+ // This is necessary for those parts that get processed with both old and new ids, such as the comments
+ // part. This is not necessary for parts such as the main document part, but this code won't malfunction
+ // in that case.
+ var partIdPair = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (partIdPair != null)
+ return;
+
+ ExternalRelationship extRel = newContentPart.ExternalRelationships.FirstOrDefault(r => r.Id == relId);
+ if (extRel != null)
+ return;
+
+ var oldPartIdPair = oldContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (oldPartIdPair != null)
+ {
+ ImagePart oldPart = oldPartIdPair.OpenXmlPart as ImagePart;
+ ImageData temp = ManageImageCopy(oldPart, newContentPart, images);
+ if (temp.ImagePart == null)
+ {
+ ImagePart newPart = null;
+ if (newContentPart is ChartDrawingPart)
+ newPart = ((ChartDrawingPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is ChartPart)
+ newPart = ((ChartPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is ChartsheetPart)
+ newPart = ((ChartsheetPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is DiagramDataPart)
+ newPart = ((DiagramDataPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is DiagramLayoutDefinitionPart)
+ newPart = ((DiagramLayoutDefinitionPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is DiagramPersistLayoutPart)
+ newPart = ((DiagramPersistLayoutPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is DrawingsPart)
+ newPart = ((DrawingsPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is HandoutMasterPart)
+ newPart = ((HandoutMasterPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is NotesMasterPart)
+ newPart = ((NotesMasterPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is NotesSlidePart)
+ newPart = ((NotesSlidePart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is RibbonAndBackstageCustomizationsPart)
+ newPart = ((RibbonAndBackstageCustomizationsPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is RibbonExtensibilityPart)
+ newPart = ((RibbonExtensibilityPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is SlideLayoutPart)
+ newPart = ((SlideLayoutPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is SlideMasterPart)
+ newPart = ((SlideMasterPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is SlidePart)
+ newPart = ((SlidePart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is ThemeOverridePart)
+ newPart = ((ThemeOverridePart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is ThemePart)
+ newPart = ((ThemePart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is VmlDrawingPart)
+ newPart = ((VmlDrawingPart)newContentPart).AddImagePart(oldPart.ContentType);
+ else if (newContentPart is WorksheetPart)
+ newPart = ((WorksheetPart)newContentPart).AddImagePart(oldPart.ContentType);
+
+ temp.ImagePart = newPart;
+ var id = newContentPart.GetIdOfPart(newPart);
+ temp.AddContentPartRelTypeResourceIdTupple(newContentPart, newPart.RelationshipType, id);
+
+ temp.WriteImage(newPart);
+ imageReference.Attribute(attributeName).Value = id;
+ }
+ else
+ {
+ var refRel = newContentPart.DataPartReferenceRelationships.FirstOrDefault(rr =>
+ {
+ var rel = temp.ContentPartRelTypeIdList.FirstOrDefault(cpr =>
+ {
+ var found = cpr.ContentPart == newContentPart && cpr.RelationshipId == rr.Id;
+ return found;
+ });
+ if (rel != null)
+ return true;
+ return false;
+ });
+ if (refRel != null)
+ {
+ imageReference.Attribute(attributeName).Value = temp.ContentPartRelTypeIdList.First(cpr =>
+ {
+ var found = cpr.ContentPart == newContentPart && cpr.RelationshipId == refRel.Id;
+ return found;
+ }).RelationshipId;
+ return;
+ }
+
+ var cpr2 = temp.ContentPartRelTypeIdList.FirstOrDefault(c => c.ContentPart == newContentPart);
+ if (cpr2 != null)
+ {
+ imageReference.Attribute(attributeName).Value = cpr2.RelationshipId;
+ }
+ else
+ {
+ ImagePart imagePart = (ImagePart)temp.ImagePart;
+ var existingImagePart = newContentPart.AddPart<ImagePart>(imagePart);
+ var newId = newContentPart.GetIdOfPart(existingImagePart);
+ temp.AddContentPartRelTypeResourceIdTupple(newContentPart, imagePart.RelationshipType, newId);
+ imageReference.Attribute(attributeName).Value = newId;
+ }
+
+ }
+ }
+ else
+ {
+ ExternalRelationship er = oldContentPart.ExternalRelationships.FirstOrDefault(r => r.Id == relId);
+ if (er != null)
+ {
+ ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri);
+ imageReference.Attribute(R.id).Value = newEr.Id;
+ }
+ else
+ {
+ var fromPart = newContentPart.OpenXmlPackage.Package.GetParts().FirstOrDefault(p => p.Uri == newContentPart.Uri);
+ fromPart.CreateRelationship(new Uri("NULL", UriKind.RelativeOrAbsolute), System.IO.Packaging.TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", relId);
+ }
+ }
+ }
+
+ private static void CopyRelatedMedia(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement imageReference, XName attributeName,
+ List<MediaData> mediaList, string mediaRelationshipType)
+ {
+ string relId = (string)imageReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ // First look to see if this relId has already been added to the new document.
+ var existingDataPartRefRel2 = newContentPart.DataPartReferenceRelationships.FirstOrDefault(dpr => dpr.Id == relId);
+ if (existingDataPartRefRel2 != null)
+ return;
+
+ var oldRel = oldContentPart.DataPartReferenceRelationships.FirstOrDefault(dpr => dpr.Id == relId);
+ if (oldRel == null)
+ return;
+
+ DataPart oldPart = oldRel.DataPart;
+ MediaData temp = ManageMediaCopy(oldPart, mediaList);
+ if (temp.DataPart == null)
+ {
+ var ct = oldPart.ContentType;
+ var ext = Path.GetExtension(oldPart.Uri.OriginalString);
+ MediaDataPart newPart = newContentPart.OpenXmlPackage.CreateMediaDataPart(ct, ext);
+ newPart.FeedData(oldPart.GetStream());
+ string id = null;
+ string relationshipType = null;
+
+ if (mediaRelationshipType == "media")
+ {
+ MediaReferenceRelationship mrr = null;
+
+ if (newContentPart is SlidePart)
+ mrr = ((SlidePart)newContentPart).AddMediaReferenceRelationship(newPart);
+ else if (newContentPart is SlideLayoutPart)
+ mrr = ((SlideLayoutPart)newContentPart).AddMediaReferenceRelationship(newPart);
+ else if (newContentPart is SlideMasterPart)
+ mrr = ((SlideMasterPart)newContentPart).AddMediaReferenceRelationship(newPart);
+
+ id = mrr.Id;
+ relationshipType = "http://schemas.microsoft.com/office/2007/relationships/media";
+ }
+ else if (mediaRelationshipType == "video")
+ {
+ VideoReferenceRelationship vrr = null;
+
+ if (newContentPart is SlidePart)
+ vrr = ((SlidePart)newContentPart).AddVideoReferenceRelationship(newPart);
+ else if (newContentPart is HandoutMasterPart)
+ vrr = ((HandoutMasterPart)newContentPart).AddVideoReferenceRelationship(newPart);
+ else if (newContentPart is NotesMasterPart)
+ vrr = ((NotesMasterPart)newContentPart).AddVideoReferenceRelationship(newPart);
+ else if (newContentPart is NotesSlidePart)
+ vrr = ((NotesSlidePart)newContentPart).AddVideoReferenceRelationship(newPart);
+ else if (newContentPart is SlideLayoutPart)
+ vrr = ((SlideLayoutPart)newContentPart).AddVideoReferenceRelationship(newPart);
+ else if (newContentPart is SlideMasterPart)
+ vrr = ((SlideMasterPart)newContentPart).AddVideoReferenceRelationship(newPart);
+
+ id = vrr.Id;
+ relationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";
+ }
+ temp.DataPart = newPart;
+ temp.AddContentPartRelTypeResourceIdTupple(newContentPart, relationshipType, id);
+ imageReference.Attribute(attributeName).Value = id;
+ }
+ else
+ {
+ string desiredRelType = null;
+ if (mediaRelationshipType == "media")
+ desiredRelType = "http://schemas.microsoft.com/office/2007/relationships/media";
+ if (mediaRelationshipType == "video")
+ desiredRelType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";
+ var existingRel = temp.ContentPartRelTypeIdList.FirstOrDefault(cp => cp.ContentPart == newContentPart && cp.RelationshipType == desiredRelType);
+ if (existingRel != null)
+ {
+ imageReference.Attribute(attributeName).Value = existingRel.RelationshipId;
+ }
+ else
+ {
+ MediaDataPart newPart = (MediaDataPart)temp.DataPart;
+ string id = null;
+ string relationshipType = null;
+ if (mediaRelationshipType == "media")
+ {
+ MediaReferenceRelationship mrr = null;
+
+ if (newContentPart is SlidePart)
+ mrr = ((SlidePart)newContentPart).AddMediaReferenceRelationship(newPart);
+ if (newContentPart is SlideLayoutPart)
+ mrr = ((SlideLayoutPart)newContentPart).AddMediaReferenceRelationship(newPart);
+ if (newContentPart is SlideMasterPart)
+ mrr = ((SlideMasterPart)newContentPart).AddMediaReferenceRelationship(newPart);
+
+ id = mrr.Id;
+ relationshipType = mrr.RelationshipType;
+ }
+ else if (mediaRelationshipType == "video")
+ {
+ VideoReferenceRelationship vrr = null;
+
+ if (newContentPart is SlidePart)
+ vrr = ((SlidePart)newContentPart).AddVideoReferenceRelationship(newPart);
+ if (newContentPart is HandoutMasterPart)
+ vrr = ((HandoutMasterPart)newContentPart).AddVideoReferenceRelationship(newPart);
+ if (newContentPart is NotesMasterPart)
+ vrr = ((NotesMasterPart)newContentPart).AddVideoReferenceRelationship(newPart);
+ if (newContentPart is NotesSlidePart)
+ vrr = ((NotesSlidePart)newContentPart).AddVideoReferenceRelationship(newPart);
+ if (newContentPart is SlideLayoutPart)
+ vrr = ((SlideLayoutPart)newContentPart).AddVideoReferenceRelationship(newPart);
+ if (newContentPart is SlideMasterPart)
+ vrr = ((SlideMasterPart)newContentPart).AddVideoReferenceRelationship(newPart);
+
+ id = vrr.Id;
+ relationshipType = vrr.RelationshipType;
+ }
+ temp.AddContentPartRelTypeResourceIdTupple(newContentPart, relationshipType, id);
+ imageReference.Attribute(attributeName).Value = id;
+ }
+ }
+ }
+
+ private static void CopyRelatedMediaExternalRelationship(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement imageReference, XName attributeName,
+ string mediaRelationshipType)
+ {
+ string relId = (string)imageReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ var existingExternalReference = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (existingExternalReference != null)
+ return;
+
+ var oldRel = oldContentPart.ExternalRelationships.FirstOrDefault(dpr => dpr.Id == relId);
+ if (oldRel == null)
+ return;
+
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ newContentPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newId);
+
+ imageReference.Attribute(attributeName).Value = newId;
+ }
+
+
+ private static void CopyInkPart(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement contentPartReference, XName attributeName)
+ {
+ string relId = (string)contentPartReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ var tempPartIdPair = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair != null)
+ return;
+
+ var tempEr = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr != null)
+ return;
+
+ var oldPart = oldContentPart.GetPartById(relId);
+
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ CustomXmlPart newPart = newContentPart.AddNewPart<CustomXmlPart>("application/inkml+xml", newId);
+
+ newPart.FeedData(oldPart.GetStream());
+ contentPartReference.Attribute(attributeName).Value = newId;
+ }
+
+ private static void CopyActiveXPart(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement activeXPartReference, XName attributeName)
+ {
+ string relId = (string)activeXPartReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ var tempPartIdPair = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair != null)
+ return;
+
+ var oldPart = oldContentPart.GetPartById(relId);
+
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ EmbeddedControlPersistencePart newPart = newContentPart.AddNewPart<EmbeddedControlPersistencePart>("application/vnd.ms-office.activeX+xml", newId);
+
+ newPart.FeedData(oldPart.GetStream());
+ activeXPartReference.Attribute(attributeName).Value = newId;
+
+ if (newPart.ContentType == "application/vnd.ms-office.activeX+xml")
+ {
+ XDocument axc = newPart.GetXDocument();
+ if (axc.Root.Attribute(R.id) != null)
+ {
+ var oldPersistencePart = oldPart.GetPartById((string)axc.Root.Attribute(R.id));
+
+ var newId2 = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ EmbeddedControlPersistenceBinaryDataPart newPersistencePart = newPart.AddNewPart<EmbeddedControlPersistenceBinaryDataPart>("application/vnd.ms-office.activeX", newId2);
+
+ newPersistencePart.FeedData(oldPersistencePart.GetStream());
+ axc.Root.Attribute(R.id).Value = newId2;
+ newPart.PutXDocument();
+ }
+ }
+ }
+
+ private static void CopyLegacyDiagramText(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement textdataReference, XName attributeName)
+ {
+ string relId = (string)textdataReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ var tempPartIdPair = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair != null)
+ return;
+
+ var oldPart = oldContentPart.GetPartById(relId);
+
+ var newId = "R" + Guid.NewGuid().ToString().Replace("-", "").Substring(0, 16);
+ LegacyDiagramTextPart newPart = newContentPart.AddNewPart<LegacyDiagramTextPart>(newId);
+
+ newPart.FeedData(oldPart.GetStream());
+ textdataReference.Attribute(attributeName).Value = newId;
+ }
+
+ private static void CopyExtendedPart(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement extendedReference, XName attributeName)
+ {
+ string relId = (string)extendedReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+ try
+ {
+ // First look to see if this relId has already been added to the new document.
+ // This is necessary for those parts that get processed with both old and new ids, such as the comments
+ // part. This is not necessary for parts such as the main document part, but this code won't malfunction
+ // in that case.
+ var tempPartIdPair = newContentPart.Parts.FirstOrDefault(p => p.RelationshipId == relId);
+ if (tempPartIdPair != null)
+ return;
+
+ var tempEr = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (tempEr != null)
+ return;
+
+ ExtendedPart oldPart = (ExtendedPart)oldContentPart.GetPartById(relId);
+ FileInfo fileInfo = new FileInfo(oldPart.Uri.OriginalString);
+ ExtendedPart newPart = null;
+
+#if !NET35
+ if (newContentPart is ChartColorStylePart)
+ newPart = ((ChartColorStylePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else
+#endif
+ if (newContentPart is ChartDrawingPart)
+ newPart = ((ChartDrawingPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ChartPart)
+ newPart = ((ChartPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ChartsheetPart)
+ newPart = ((ChartsheetPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+#if !NET35
+ else if (newContentPart is ChartStylePart)
+ newPart = ((ChartStylePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+#endif
+ else if (newContentPart is CommentAuthorsPart)
+ newPart = ((CommentAuthorsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ConnectionsPart)
+ newPart = ((ConnectionsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ControlPropertiesPart)
+ newPart = ((ControlPropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CoreFilePropertiesPart)
+ newPart = ((CoreFilePropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomDataPart)
+ newPart = ((CustomDataPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomDataPropertiesPart)
+ newPart = ((CustomDataPropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomFilePropertiesPart)
+ newPart = ((CustomFilePropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomizationPart)
+ newPart = ((CustomizationPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomPropertyPart)
+ newPart = ((CustomPropertyPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomUIPart)
+ newPart = ((CustomUIPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomXmlMappingsPart)
+ newPart = ((CustomXmlMappingsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomXmlPart)
+ newPart = ((CustomXmlPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is CustomXmlPropertiesPart)
+ newPart = ((CustomXmlPropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DiagramColorsPart)
+ newPart = ((DiagramColorsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DiagramDataPart)
+ newPart = ((DiagramDataPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DiagramLayoutDefinitionPart)
+ newPart = ((DiagramLayoutDefinitionPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DiagramPersistLayoutPart)
+ newPart = ((DiagramPersistLayoutPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DiagramStylePart)
+ newPart = ((DiagramStylePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DigitalSignatureOriginPart)
+ newPart = ((DigitalSignatureOriginPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is DrawingsPart)
+ newPart = ((DrawingsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is EmbeddedControlPersistenceBinaryDataPart)
+ newPart = ((EmbeddedControlPersistenceBinaryDataPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is EmbeddedControlPersistencePart)
+ newPart = ((EmbeddedControlPersistencePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is EmbeddedObjectPart)
+ newPart = ((EmbeddedObjectPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is EmbeddedPackagePart)
+ newPart = ((EmbeddedPackagePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ExtendedFilePropertiesPart)
+ newPart = ((ExtendedFilePropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ExtendedPart)
+ newPart = ((ExtendedPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is FontPart)
+ newPart = ((FontPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is FontTablePart)
+ newPart = ((FontTablePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is HandoutMasterPart)
+ newPart = ((HandoutMasterPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is InternationalMacroSheetPart)
+ newPart = ((InternationalMacroSheetPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is LegacyDiagramTextInfoPart)
+ newPart = ((LegacyDiagramTextInfoPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is LegacyDiagramTextPart)
+ newPart = ((LegacyDiagramTextPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is MacroSheetPart)
+ newPart = ((MacroSheetPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is NotesMasterPart)
+ newPart = ((NotesMasterPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is NotesSlidePart)
+ newPart = ((NotesSlidePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is PresentationPart)
+ newPart = ((PresentationPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is PresentationPropertiesPart)
+ newPart = ((PresentationPropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is QuickAccessToolbarCustomizationsPart)
+ newPart = ((QuickAccessToolbarCustomizationsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is RibbonAndBackstageCustomizationsPart)
+ newPart = ((RibbonAndBackstageCustomizationsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is RibbonExtensibilityPart)
+ newPart = ((RibbonExtensibilityPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is SingleCellTablePart)
+ newPart = ((SingleCellTablePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is SlideCommentsPart)
+ newPart = ((SlideCommentsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is SlideLayoutPart)
+ newPart = ((SlideLayoutPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is SlideMasterPart)
+ newPart = ((SlideMasterPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is SlidePart)
+ newPart = ((SlidePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is SlideSyncDataPart)
+ newPart = ((SlideSyncDataPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is StyleDefinitionsPart)
+ newPart = ((StyleDefinitionsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is StylesPart)
+ newPart = ((StylesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is StylesWithEffectsPart)
+ newPart = ((StylesWithEffectsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is TableDefinitionPart)
+ newPart = ((TableDefinitionPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is TableStylesPart)
+ newPart = ((TableStylesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ThemeOverridePart)
+ newPart = ((ThemeOverridePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ThemePart)
+ newPart = ((ThemePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ThumbnailPart)
+ newPart = ((ThumbnailPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+#if !NET35
+ else if (newContentPart is TimeLineCachePart)
+ newPart = ((TimeLineCachePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is TimeLinePart)
+ newPart = ((TimeLinePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+#endif
+ else if (newContentPart is UserDefinedTagsPart)
+ newPart = ((UserDefinedTagsPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is VbaDataPart)
+ newPart = ((VbaDataPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is VbaProjectPart)
+ newPart = ((VbaProjectPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is ViewPropertiesPart)
+ newPart = ((ViewPropertiesPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is VmlDrawingPart)
+ newPart = ((VmlDrawingPart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+ else if (newContentPart is XmlSignaturePart)
+ newPart = ((XmlSignaturePart)newContentPart).AddExtendedPart(oldPart.RelationshipType, oldPart.ContentType, fileInfo.Extension);
+
+ relId = newContentPart.GetIdOfPart(newPart);
+ newPart.FeedData(oldPart.GetStream());
+ extendedReference.Attribute(attributeName).Value = relId;
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ try
+ {
+ ExternalRelationship er = oldContentPart.GetExternalRelationship(relId);
+ ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri);
+ extendedReference.Attribute(R.id).Value = newEr.Id;
+ }
+ catch (KeyNotFoundException)
+ {
+ var fromPart = newContentPart.OpenXmlPackage.Package.GetParts().FirstOrDefault(p => p.Uri == newContentPart.Uri);
+ fromPart.CreateRelationship(new Uri("NULL", UriKind.RelativeOrAbsolute), System.IO.Packaging.TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image", relId);
+ }
+ }
+ }
+
+ // General function for handling images that tries to use an existing image if they are the same
+ private static ImageData ManageImageCopy(ImagePart oldImage, OpenXmlPart newContentPart, List<ImageData> images)
+ {
+ ImageData oldImageData = new ImageData(oldImage);
+ foreach (ImageData item in images)
+ {
+ if (item.Compare(oldImageData))
+ return item;
+ }
+ images.Add(oldImageData);
+ return oldImageData;
+ }
+
+ // General function for handling media that tries to use an existing media item if they are the same
+ private static MediaData ManageMediaCopy(DataPart oldMedia, List<MediaData> mediaList)
+ {
+ MediaData oldMediaData = new MediaData(oldMedia);
+ foreach (MediaData item in mediaList)
+ {
+ if (item.Compare(oldMediaData))
+ return item;
+ }
+ mediaList.Add(oldMediaData);
+ return oldMediaData;
+ }
+
+ private static void CopyRelatedSound(PresentationDocument newDocument, OpenXmlPart oldContentPart, OpenXmlPart newContentPart,
+ XElement soundReference, XName attributeName)
+ {
+ string relId = (string)soundReference.Attribute(attributeName);
+ if (string.IsNullOrEmpty(relId))
+ return;
+
+ ExternalRelationship alreadyExistingExternalRelationship = newContentPart.ExternalRelationships.FirstOrDefault(er => er.Id == relId);
+ if (alreadyExistingExternalRelationship != null)
+ return;
+
+ ReferenceRelationship alreadyExistingReferenceRelationship = newContentPart.DataPartReferenceRelationships.FirstOrDefault(er => er.Id == relId);
+ if (alreadyExistingReferenceRelationship != null)
+ return;
+
+ if (oldContentPart.GetReferenceRelationship(relId) is AudioReferenceRelationship)
+ {
+ AudioReferenceRelationship temp = (AudioReferenceRelationship)oldContentPart.GetReferenceRelationship(relId);
+ MediaDataPart newSound = newDocument.CreateMediaDataPart(temp.DataPart.ContentType);
+ newSound.FeedData(temp.DataPart.GetStream());
+ AudioReferenceRelationship newRel = null;
+
+ if (newContentPart is SlidePart)
+ newRel = ((SlidePart)newContentPart).AddAudioReferenceRelationship(newSound);
+ else if (newContentPart is SlideLayoutPart)
+ newRel = ((SlideLayoutPart)newContentPart).AddAudioReferenceRelationship(newSound);
+ else if (newContentPart is SlideMasterPart)
+ newRel = ((SlideMasterPart)newContentPart).AddAudioReferenceRelationship(newSound);
+ else if (newContentPart is HandoutMasterPart)
+ newRel = ((HandoutMasterPart)newContentPart).AddAudioReferenceRelationship(newSound);
+ else if (newContentPart is NotesMasterPart)
+ newRel = ((NotesMasterPart)newContentPart).AddAudioReferenceRelationship(newSound);
+ else if (newContentPart is NotesSlidePart)
+ newRel = ((NotesSlidePart)newContentPart).AddAudioReferenceRelationship(newSound);
+ soundReference.Attribute(attributeName).Value = newRel.Id;
+ }
+ if (oldContentPart.GetReferenceRelationship(relId) is MediaReferenceRelationship)
+ {
+ MediaReferenceRelationship temp = (MediaReferenceRelationship)oldContentPart.GetReferenceRelationship(relId);
+ MediaDataPart newSound = newDocument.CreateMediaDataPart(temp.DataPart.ContentType);
+ newSound.FeedData(temp.DataPart.GetStream());
+ MediaReferenceRelationship newRel = null;
+
+ if (newContentPart is SlidePart)
+ newRel = ((SlidePart)newContentPart).AddMediaReferenceRelationship(newSound);
+ else if (newContentPart is SlideLayoutPart)
+ newRel = ((SlideLayoutPart)newContentPart).AddMediaReferenceRelationship(newSound);
+ else if (newContentPart is SlideMasterPart)
+ newRel = ((SlideMasterPart)newContentPart).AddMediaReferenceRelationship(newSound);
+ soundReference.Attribute(attributeName).Value = newRel.Id;
+ }
+ }
+ }
+
+ public class PresentationBuilderException : Exception
+ {
+ public PresentationBuilderException(string message) : base(message) { }
+ }
+
+ public class PresentationBuilderInternalException : Exception
+ {
+ public PresentationBuilderInternalException(string message) : base(message) { }
+ }
+}
diff --git a/OpenXmlPowerTools/Properties/AssemblyInfo.cs b/OpenXmlPowerTools/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e432882
--- /dev/null
+++ b/OpenXmlPowerTools/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("OpenXmlPowerTools.Tests")]
diff --git a/OpenXmlPowerTools/PtOpenXmlDocument.cs b/OpenXmlPowerTools/PtOpenXmlDocument.cs
new file mode 100644
index 0000000..1f2574c
--- /dev/null
+++ b/OpenXmlPowerTools/PtOpenXmlDocument.cs
@@ -0,0 +1,772 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+/*
+Here is modification of a WmlDocument:
+ public static WmlDocument SimplifyMarkup(WmlDocument doc, SimplifyMarkupSettings settings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ {
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ SimplifyMarkup(document, settings);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+Here is read-only of a WmlDocument:
+
+ public static string GetBackgroundColor(WmlDocument doc)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ XDocument mainDocument = document.MainDocumentPart.GetXDocument();
+ XElement backgroundElement = mainDocument.Descendants(W.background).FirstOrDefault();
+ return (backgroundElement == null) ? string.Empty : backgroundElement.Attribute(W.color).Value;
+ }
+ }
+
+Here is creating a new WmlDocument:
+
+ private OpenXmlPowerToolsDocument CreateSplitDocument(WordprocessingDocument source, List<XElement> contents, string newFileName)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreateWordprocessingDocument())
+ {
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ DocumentBuilder.FixRanges(source.MainDocumentPart.GetXDocument(), contents);
+ PowerToolsExtensions.SetContent(document, contents);
+ }
+ OpenXmlPowerToolsDocument newDoc = streamDoc.GetModifiedDocument();
+ newDoc.FileName = newFileName;
+ return newDoc;
+ }
+ }
+*/
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using System.IO.Packaging;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class PowerToolsDocumentException : Exception
+ {
+ public PowerToolsDocumentException(string message) : base(message) { }
+ }
+ public class PowerToolsInvalidDataException : Exception
+ {
+ public PowerToolsInvalidDataException(string message) : base(message) { }
+ }
+
+ public class OpenXmlPowerToolsDocument
+ {
+ public string FileName { get; set; }
+ public byte[] DocumentByteArray { get; set; }
+
+ public static OpenXmlPowerToolsDocument FromFileName(string fileName)
+ {
+ byte[] bytes = File.ReadAllBytes(fileName);
+ Type type;
+ try
+ {
+ type = GetDocumentType(bytes);
+ }
+ catch (FileFormatException)
+ {
+ throw new PowerToolsDocumentException("Not an Open XML document.");
+ }
+ if (type == typeof(WordprocessingDocument))
+ return new WmlDocument(fileName, bytes);
+ if (type == typeof(SpreadsheetDocument))
+ return new SmlDocument(fileName, bytes);
+ if (type == typeof(PresentationDocument))
+ return new PmlDocument(fileName, bytes);
+ if (type == typeof(Package))
+ {
+ OpenXmlPowerToolsDocument pkg = new OpenXmlPowerToolsDocument(bytes);
+ pkg.FileName = fileName;
+ return pkg;
+ }
+ throw new PowerToolsDocumentException("Not an Open XML document.");
+ }
+
+ public static OpenXmlPowerToolsDocument FromDocument(OpenXmlPowerToolsDocument doc)
+ {
+ Type type = doc.GetDocumentType();
+ if (type == typeof(WordprocessingDocument))
+ return new WmlDocument(doc);
+ if (type == typeof(SpreadsheetDocument))
+ return new SmlDocument(doc);
+ if (type == typeof(PresentationDocument))
+ return new PmlDocument(doc);
+ return null; // This should not be possible from a valid OpenXmlPowerToolsDocument object
+ }
+
+ public OpenXmlPowerToolsDocument(OpenXmlPowerToolsDocument original)
+ {
+ DocumentByteArray = new byte[original.DocumentByteArray.Length];
+ Array.Copy(original.DocumentByteArray, DocumentByteArray, original.DocumentByteArray.Length);
+ FileName = original.FileName;
+ }
+
+ public OpenXmlPowerToolsDocument(OpenXmlPowerToolsDocument original, bool convertToTransitional)
+ {
+ if (convertToTransitional)
+ {
+ ConvertToTransitional(original.FileName, original.DocumentByteArray);
+ }
+ else
+ {
+ DocumentByteArray = new byte[original.DocumentByteArray.Length];
+ Array.Copy(original.DocumentByteArray, DocumentByteArray, original.DocumentByteArray.Length);
+ FileName = original.FileName;
+ }
+ }
+
+ public OpenXmlPowerToolsDocument(string fileName)
+ {
+ this.FileName = fileName;
+ DocumentByteArray = File.ReadAllBytes(fileName);
+ }
+
+ public OpenXmlPowerToolsDocument(string fileName, bool convertToTransitional)
+ {
+ this.FileName = fileName;
+
+ if (convertToTransitional)
+ {
+ var tempByteArray = File.ReadAllBytes(fileName);
+ ConvertToTransitional(fileName, tempByteArray);
+ }
+ else
+ {
+ this.FileName = fileName;
+ DocumentByteArray = File.ReadAllBytes(fileName);
+ }
+ }
+
+ private void ConvertToTransitional(string fileName, byte[] tempByteArray)
+ {
+ Type type;
+ try
+ {
+ type = GetDocumentType(tempByteArray);
+ }
+ catch (FileFormatException)
+ {
+ throw new PowerToolsDocumentException("Not an Open XML document.");
+ }
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(tempByteArray, 0, tempByteArray.Length);
+ if (type == typeof(WordprocessingDocument))
+ {
+ using (WordprocessingDocument sDoc = WordprocessingDocument.Open(ms, true))
+ {
+ // following code forces the SDK to serialize
+ foreach (var part in sDoc.Parts)
+ {
+ try
+ {
+ var z = part.OpenXmlPart.RootElement;
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ }
+ }
+ else if (type == typeof(SpreadsheetDocument))
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, true))
+ {
+ // following code forces the SDK to serialize
+ foreach (var part in sDoc.Parts)
+ {
+ try
+ {
+ var z = part.OpenXmlPart.RootElement;
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ }
+ }
+ else if (type == typeof(PresentationDocument))
+ {
+ using (PresentationDocument sDoc = PresentationDocument.Open(ms, true))
+ {
+ // following code forces the SDK to serialize
+ foreach (var part in sDoc.Parts)
+ {
+ try
+ {
+ var z = part.OpenXmlPart.RootElement;
+ }
+ catch (Exception)
+ {
+ continue;
+ }
+ }
+ }
+ }
+ this.FileName = fileName;
+ DocumentByteArray = ms.ToArray();
+ }
+ }
+
+ public OpenXmlPowerToolsDocument(byte[] byteArray)
+ {
+ DocumentByteArray = new byte[byteArray.Length];
+ Array.Copy(byteArray, DocumentByteArray, byteArray.Length);
+ this.FileName = null;
+ }
+
+ public OpenXmlPowerToolsDocument(byte[] byteArray, bool convertToTransitional)
+ {
+ if (convertToTransitional)
+ {
+ ConvertToTransitional(null, byteArray);
+ }
+ else
+ {
+ DocumentByteArray = new byte[byteArray.Length];
+ Array.Copy(byteArray, DocumentByteArray, byteArray.Length);
+ this.FileName = null;
+ }
+ }
+
+ public OpenXmlPowerToolsDocument(string fileName, MemoryStream memStream)
+ {
+ FileName = fileName;
+ DocumentByteArray = new byte[memStream.Length];
+ Array.Copy(memStream.GetBuffer(), DocumentByteArray, memStream.Length);
+ }
+
+ public OpenXmlPowerToolsDocument(string fileName, MemoryStream memStream, bool convertToTransitional)
+ {
+ if (convertToTransitional)
+ {
+ ConvertToTransitional(fileName, memStream.ToArray());
+ }
+ else
+ {
+ FileName = fileName;
+ DocumentByteArray = new byte[memStream.Length];
+ Array.Copy(memStream.GetBuffer(), DocumentByteArray, memStream.Length);
+ }
+ }
+
+ public string GetName()
+ {
+ if (FileName == null)
+ return "Unnamed Document";
+ FileInfo file = new FileInfo(FileName);
+ return file.Name;
+ }
+
+ public void SaveAs(string fileName)
+ {
+ File.WriteAllBytes(fileName, DocumentByteArray);
+ }
+
+ public void Save()
+ {
+ if (this.FileName == null)
+ throw new InvalidOperationException("Attempting to Save a document that has no file name. Use SaveAs instead.");
+ File.WriteAllBytes(this.FileName, DocumentByteArray);
+ }
+
+ public void WriteByteArray(Stream stream)
+ {
+ stream.Write(DocumentByteArray, 0, DocumentByteArray.Length);
+ }
+
+ public Type GetDocumentType()
+ {
+ return GetDocumentType(DocumentByteArray);
+ }
+
+ private static Type GetDocumentType(byte[] bytes)
+ {
+ using (MemoryStream stream = new MemoryStream())
+ {
+ stream.Write(bytes, 0, bytes.Length);
+ using (Package package = Package.Open(stream, FileMode.Open))
+ {
+ PackageRelationship relationship = package.GetRelationshipsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument").FirstOrDefault();
+ if (relationship == null)
+ relationship = package.GetRelationshipsByType("http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument").FirstOrDefault();
+ if (relationship != null)
+ {
+ PackagePart part = package.GetPart(PackUriHelper.ResolvePartUri(relationship.SourceUri, relationship.TargetUri));
+ switch (part.ContentType)
+ {
+ case "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml":
+ case "application/vnd.ms-word.document.macroEnabled.main+xml":
+ case "application/vnd.ms-word.template.macroEnabledTemplate.main+xml":
+ case "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml":
+ return typeof(WordprocessingDocument);
+ case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml":
+ case "application/vnd.ms-excel.sheet.macroEnabled.main+xml":
+ case "application/vnd.ms-excel.template.macroEnabled.main+xml":
+ case "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml":
+ return typeof(SpreadsheetDocument);
+ case "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml":
+ case "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml":
+ case "application/vnd.ms-powerpoint.template.macroEnabled.main+xml":
+ case "application/vnd.ms-powerpoint.addin.macroEnabled.main+xml":
+ case "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml":
+ case "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml":
+ return typeof(PresentationDocument);
+ }
+ return typeof(Package);
+ }
+ return null;
+ }
+ }
+ }
+
+ public static void SavePartAs(OpenXmlPart part, string filePath)
+ {
+ Stream partStream = part.GetStream(FileMode.Open, FileAccess.Read);
+ byte[] partContent = new byte[partStream.Length];
+ partStream.Read(partContent, 0, (int)partStream.Length);
+
+ File.WriteAllBytes(filePath, partContent);
+ }
+ }
+
+ public partial class WmlDocument : OpenXmlPowerToolsDocument
+ {
+ public WmlDocument(OpenXmlPowerToolsDocument original)
+ : base(original)
+ {
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ }
+
+ public WmlDocument(OpenXmlPowerToolsDocument original, bool convertToTransitional)
+ : base(original, convertToTransitional)
+ {
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ }
+
+ public WmlDocument(string fileName)
+ : base(fileName)
+ {
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ }
+
+ public WmlDocument(string fileName, bool convertToTransitional)
+ : base(fileName, convertToTransitional)
+ {
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ }
+
+ public WmlDocument(string fileName, byte[] byteArray)
+ : base(byteArray)
+ {
+ FileName = fileName;
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ }
+
+ public WmlDocument(string fileName, byte[] byteArray, bool convertToTransitional)
+ : base(byteArray, convertToTransitional)
+ {
+ FileName = fileName;
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ }
+
+ public WmlDocument(string fileName, MemoryStream memStream)
+ : base(fileName, memStream)
+ {
+ }
+
+ public WmlDocument(string fileName, MemoryStream memStream, bool convertToTransitional)
+ : base(fileName, memStream, convertToTransitional)
+ {
+ }
+ }
+
+ public partial class SmlDocument : OpenXmlPowerToolsDocument
+ {
+ public SmlDocument(OpenXmlPowerToolsDocument original)
+ : base(original)
+ {
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ }
+
+ public SmlDocument(OpenXmlPowerToolsDocument original, bool convertToTransitional)
+ : base(original, convertToTransitional)
+ {
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ }
+
+ public SmlDocument(string fileName)
+ : base(fileName)
+ {
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ }
+
+ public SmlDocument(string fileName, bool convertToTransitional)
+ : base(fileName, convertToTransitional)
+ {
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ }
+
+ public SmlDocument(string fileName, byte[] byteArray)
+ : base(byteArray)
+ {
+ FileName = fileName;
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ }
+
+ public SmlDocument(string fileName, byte[] byteArray, bool convertToTransitional)
+ : base(byteArray, convertToTransitional)
+ {
+ FileName = fileName;
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ }
+
+ public SmlDocument(string fileName, MemoryStream memStream)
+ : base(fileName, memStream)
+ {
+ }
+
+ public SmlDocument(string fileName, MemoryStream memStream, bool convertToTransitional)
+ : base(fileName, memStream, convertToTransitional)
+ {
+ }
+ }
+
+ public partial class PmlDocument : OpenXmlPowerToolsDocument
+ {
+ public PmlDocument(OpenXmlPowerToolsDocument original)
+ : base(original)
+ {
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ }
+
+ public PmlDocument(OpenXmlPowerToolsDocument original, bool convertToTransitional)
+ : base(original, convertToTransitional)
+ {
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ }
+
+ public PmlDocument(string fileName)
+ : base(fileName)
+ {
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ }
+
+ public PmlDocument(string fileName, bool convertToTransitional)
+ : base(fileName, convertToTransitional)
+ {
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ }
+
+ public PmlDocument(string fileName, byte[] byteArray)
+ : base(byteArray)
+ {
+ FileName = fileName;
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ }
+
+ public PmlDocument(string fileName, byte[] byteArray, bool convertToTransitional)
+ : base(byteArray, convertToTransitional)
+ {
+ FileName = fileName;
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ }
+
+ public PmlDocument(string fileName, MemoryStream memStream)
+ : base(fileName, memStream)
+ {
+ }
+
+ public PmlDocument(string fileName, MemoryStream memStream, bool convertToTransitional)
+ : base(fileName, memStream, convertToTransitional)
+ {
+ }
+ }
+
+ public class OpenXmlMemoryStreamDocument : IDisposable
+ {
+ private OpenXmlPowerToolsDocument Document;
+ private MemoryStream DocMemoryStream;
+ private Package DocPackage;
+
+ public OpenXmlMemoryStreamDocument(OpenXmlPowerToolsDocument doc)
+ {
+ Document = doc;
+ DocMemoryStream = new MemoryStream();
+ DocMemoryStream.Write(doc.DocumentByteArray, 0, doc.DocumentByteArray.Length);
+ try
+ {
+ DocPackage = Package.Open(DocMemoryStream, FileMode.Open);
+ }
+ catch (Exception e)
+ {
+ throw new PowerToolsDocumentException(e.Message);
+ }
+ }
+
+ internal OpenXmlMemoryStreamDocument(MemoryStream stream)
+ {
+ DocMemoryStream = stream;
+ try
+ {
+ DocPackage = Package.Open(DocMemoryStream, FileMode.Open);
+ }
+ catch (Exception e)
+ {
+ throw new PowerToolsDocumentException(e.Message);
+ }
+ }
+
+ public static OpenXmlMemoryStreamDocument CreateWordprocessingDocument()
+ {
+ MemoryStream stream = new MemoryStream();
+ using (WordprocessingDocument doc = WordprocessingDocument.Create(stream, DocumentFormat.OpenXml.WordprocessingDocumentType.Document))
+ {
+ doc.AddMainDocumentPart();
+ doc.MainDocumentPart.PutXDocument(new XDocument(
+ new XElement(W.document,
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XElement(W.body))));
+ doc.Close();
+ return new OpenXmlMemoryStreamDocument(stream);
+ }
+ }
+ public static OpenXmlMemoryStreamDocument CreateSpreadsheetDocument()
+ {
+ MemoryStream stream = new MemoryStream();
+ using (SpreadsheetDocument doc = SpreadsheetDocument.Create(stream, DocumentFormat.OpenXml.SpreadsheetDocumentType.Workbook))
+ {
+ doc.AddWorkbookPart();
+ XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ XNamespace relationshipsns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
+ doc.WorkbookPart.PutXDocument(new XDocument(
+ new XElement(ns + "workbook",
+ new XAttribute("xmlns", ns),
+ new XAttribute(XNamespace.Xmlns + "r", relationshipsns),
+ new XElement(ns + "sheets"))));
+ doc.Close();
+ return new OpenXmlMemoryStreamDocument(stream);
+ }
+ }
+ public static OpenXmlMemoryStreamDocument CreatePresentationDocument()
+ {
+ MemoryStream stream = new MemoryStream();
+ using (PresentationDocument doc = PresentationDocument.Create(stream, DocumentFormat.OpenXml.PresentationDocumentType.Presentation))
+ {
+ doc.AddPresentationPart();
+ XNamespace ns = "http://schemas.openxmlformats.org/presentationml/2006/main";
+ XNamespace relationshipsns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
+ XNamespace drawingns = "http://schemas.openxmlformats.org/drawingml/2006/main";
+ doc.PresentationPart.PutXDocument(new XDocument(
+ new XElement(ns + "presentation",
+ new XAttribute(XNamespace.Xmlns + "a", drawingns),
+ new XAttribute(XNamespace.Xmlns + "r", relationshipsns),
+ new XAttribute(XNamespace.Xmlns + "p", ns),
+ new XElement(ns + "sldMasterIdLst"),
+ new XElement(ns + "sldIdLst"),
+ new XElement(ns + "notesSz", new XAttribute("cx", "6858000"), new XAttribute("cy", "9144000")))));
+ doc.Close();
+ return new OpenXmlMemoryStreamDocument(stream);
+ }
+ }
+
+ public static OpenXmlMemoryStreamDocument CreatePackage()
+ {
+ MemoryStream stream = new MemoryStream();
+ Package package = Package.Open(stream, FileMode.Create);
+ package.Close();
+ return new OpenXmlMemoryStreamDocument(stream);
+ }
+
+ public Package GetPackage()
+ {
+ return DocPackage;
+ }
+
+ public WordprocessingDocument GetWordprocessingDocument()
+ {
+ try
+ {
+ if (GetDocumentType() != typeof(WordprocessingDocument))
+ throw new PowerToolsDocumentException("Not a Wordprocessing document.");
+ return WordprocessingDocument.Open(DocPackage);
+ }
+ catch (Exception e)
+ {
+ throw new PowerToolsDocumentException(e.Message);
+ }
+ }
+ public SpreadsheetDocument GetSpreadsheetDocument()
+ {
+ try
+ {
+ if (GetDocumentType() != typeof(SpreadsheetDocument))
+ throw new PowerToolsDocumentException("Not a Spreadsheet document.");
+ return SpreadsheetDocument.Open(DocPackage);
+ }
+ catch (Exception e)
+ {
+ throw new PowerToolsDocumentException(e.Message);
+ }
+ }
+
+ public PresentationDocument GetPresentationDocument()
+ {
+ try
+ {
+ if (GetDocumentType() != typeof(PresentationDocument))
+ throw new PowerToolsDocumentException("Not a Presentation document.");
+ return PresentationDocument.Open(DocPackage);
+ }
+ catch (Exception e)
+ {
+ throw new PowerToolsDocumentException(e.Message);
+ }
+ }
+
+ public Type GetDocumentType()
+ {
+ PackageRelationship relationship = DocPackage.GetRelationshipsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument").FirstOrDefault();
+ if (relationship == null)
+ relationship = DocPackage.GetRelationshipsByType("http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument").FirstOrDefault();
+ if (relationship == null)
+ throw new PowerToolsDocumentException("Not an Open XML Document.");
+ PackagePart part = DocPackage.GetPart(PackUriHelper.ResolvePartUri(relationship.SourceUri, relationship.TargetUri));
+ switch (part.ContentType)
+ {
+ case "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml":
+ case "application/vnd.ms-word.document.macroEnabled.main+xml":
+ case "application/vnd.ms-word.template.macroEnabledTemplate.main+xml":
+ case "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml":
+ return typeof(WordprocessingDocument);
+ case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml":
+ case "application/vnd.ms-excel.sheet.macroEnabled.main+xml":
+ case "application/vnd.ms-excel.template.macroEnabled.main+xml":
+ case "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml":
+ return typeof(SpreadsheetDocument);
+ case "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml":
+ case "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml":
+ case "application/vnd.ms-powerpoint.template.macroEnabled.main+xml":
+ case "application/vnd.ms-powerpoint.addin.macroEnabled.main+xml":
+ case "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml":
+ case "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml":
+ return typeof(PresentationDocument);
+ }
+ return null;
+ }
+
+ public OpenXmlPowerToolsDocument GetModifiedDocument()
+ {
+ DocPackage.Close();
+ DocPackage = null;
+ return new OpenXmlPowerToolsDocument((Document == null) ? null : Document.FileName, DocMemoryStream);
+ }
+
+ public WmlDocument GetModifiedWmlDocument()
+ {
+ DocPackage.Close();
+ DocPackage = null;
+ return new WmlDocument((Document == null) ? null : Document.FileName, DocMemoryStream);
+ }
+
+ public SmlDocument GetModifiedSmlDocument()
+ {
+ DocPackage.Close();
+ DocPackage = null;
+ return new SmlDocument((Document == null) ? null : Document.FileName, DocMemoryStream);
+ }
+
+ public PmlDocument GetModifiedPmlDocument()
+ {
+ DocPackage.Close();
+ DocPackage = null;
+ return new PmlDocument((Document == null) ? null : Document.FileName, DocMemoryStream);
+ }
+
+ public void Close()
+ {
+ Dispose(true);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ ~OpenXmlMemoryStreamDocument()
+ {
+ Dispose(false);
+ }
+
+ private void Dispose(Boolean disposing)
+ {
+ if (disposing)
+ {
+ if (DocPackage != null)
+ {
+ DocPackage.Close();
+ }
+ if (DocMemoryStream != null)
+ {
+ DocMemoryStream.Dispose();
+ }
+ }
+ if (DocPackage == null && DocMemoryStream == null)
+ return;
+ DocPackage = null;
+ DocMemoryStream = null;
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/PtOpenXmlUtil.cs b/OpenXmlPowerTools/PtOpenXmlUtil.cs
new file mode 100644
index 0000000..f812da2
--- /dev/null
+++ b/OpenXmlPowerTools/PtOpenXmlUtil.cs
@@ -0,0 +1,6036 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 3.1.10
+ * Add PtOpenXml.ListItemRun
+
+Version: 2.7.03
+ * Enhancements to support RTL
+
+Version: 2.6.00
+ * Enhancements to support HtmlConverter.cs
+
+***************************************************************************/
+
+using System;
+using System.IO;
+using System.IO.Packaging;
+using System.IO.Compression;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Linq;
+using System.Xml.Linq;
+using System.Collections.Generic;
+using DocumentFormat.OpenXml.Packaging;
+using System.Drawing;
+using Font = System.Drawing.Font;
+using FontFamily = System.Drawing.FontFamily;
+
+// ReSharper disable InconsistentNaming
+
+namespace OpenXmlPowerTools
+{
+ public static class PtOpenXmlExtensions
+ {
+ public static XDocument GetXDocument(this OpenXmlPart part)
+ {
+ if (part == null) throw new ArgumentNullException("part");
+
+ XDocument partXDocument = part.Annotation<XDocument>();
+ if (partXDocument != null) return partXDocument;
+
+ using (Stream partStream = part.GetStream())
+ {
+ if (partStream.Length == 0)
+ {
+ partXDocument = new XDocument();
+ partXDocument.Declaration = new XDeclaration("1.0", "UTF-8", "yes");
+ }
+ else
+ {
+ using (XmlReader partXmlReader = XmlReader.Create(partStream))
+ partXDocument = XDocument.Load(partXmlReader);
+ }
+ }
+
+ part.AddAnnotation(partXDocument);
+ return partXDocument;
+ }
+
+ public static XDocument GetXDocument(this OpenXmlPart part, out XmlNamespaceManager namespaceManager)
+ {
+ if (part == null) throw new ArgumentNullException("part");
+
+ namespaceManager = part.Annotation<XmlNamespaceManager>();
+ XDocument partXDocument = part.Annotation<XDocument>();
+ if (partXDocument != null)
+ {
+ if (namespaceManager != null) return partXDocument;
+
+ namespaceManager = GetManagerFromXDocument(partXDocument);
+ part.AddAnnotation(namespaceManager);
+
+ return partXDocument;
+ }
+
+ using (Stream partStream = part.GetStream())
+ {
+ if (partStream.Length == 0)
+ {
+ partXDocument = new XDocument();
+ partXDocument.Declaration = new XDeclaration("1.0", "UTF-8", "yes");
+
+ part.AddAnnotation(partXDocument);
+
+ return partXDocument;
+ }
+ else
+ {
+ using (XmlReader partXmlReader = XmlReader.Create(partStream))
+ {
+ partXDocument = XDocument.Load(partXmlReader);
+ namespaceManager = new XmlNamespaceManager(partXmlReader.NameTable);
+
+ part.AddAnnotation(partXDocument);
+ part.AddAnnotation(namespaceManager);
+
+ return partXDocument;
+ }
+ }
+ }
+ }
+
+ public static void PutXDocument(this OpenXmlPart part)
+ {
+ if (part == null) throw new ArgumentNullException("part");
+
+ XDocument partXDocument = part.GetXDocument();
+ if (partXDocument != null)
+ {
+#if true
+ using (Stream partStream = part.GetStream(FileMode.Create, FileAccess.Write))
+ using (XmlWriter partXmlWriter = XmlWriter.Create(partStream))
+ partXDocument.Save(partXmlWriter);
+#else
+ byte[] array = Encoding.UTF8.GetBytes(partXDocument.ToString(SaveOptions.DisableFormatting));
+ using (MemoryStream ms = new MemoryStream(array))
+ part.FeedData(ms);
+#endif
+ }
+ }
+
+ public static void PutXDocumentWithFormatting(this OpenXmlPart part)
+ {
+ if (part == null) throw new ArgumentNullException("part");
+
+ XDocument partXDocument = part.GetXDocument();
+ if (partXDocument != null)
+ {
+ using (Stream partStream = part.GetStream(FileMode.Create, FileAccess.Write))
+ {
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.Indent = true;
+ settings.OmitXmlDeclaration = true;
+ settings.NewLineOnAttributes = true;
+ using (XmlWriter partXmlWriter = XmlWriter.Create(partStream, settings))
+ partXDocument.Save(partXmlWriter);
+ }
+ }
+ }
+
+ public static void PutXDocument(this OpenXmlPart part, XDocument document)
+ {
+ if (part == null) throw new ArgumentNullException("part");
+ if (document == null) throw new ArgumentNullException("document");
+
+ using (Stream partStream = part.GetStream(FileMode.Create, FileAccess.Write))
+ using (XmlWriter partXmlWriter = XmlWriter.Create(partStream))
+ document.Save(partXmlWriter);
+
+ part.RemoveAnnotations<XDocument>();
+ part.AddAnnotation(document);
+ }
+
+ private static XmlNamespaceManager GetManagerFromXDocument(XDocument xDocument)
+ {
+ XmlReader reader = xDocument.CreateReader();
+ XDocument newXDoc = XDocument.Load(reader);
+
+ XElement rootElement = xDocument.Elements().FirstOrDefault();
+ rootElement.ReplaceWith(newXDoc.Root);
+
+ XmlNameTable nameTable = reader.NameTable;
+ XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
+ return namespaceManager;
+ }
+
+ public static IEnumerable<XElement> LogicalChildrenContent(this XElement element)
+ {
+ if (element.Name == W.document)
+ return element.Descendants(W.body).Take(1);
+
+ if (element.Name == W.body ||
+ element.Name == W.tc ||
+ element.Name == W.txbxContent)
+ return element
+ .DescendantsTrimmed(e =>
+ e.Name == W.tbl ||
+ e.Name == W.p)
+ .Where(e =>
+ e.Name == W.p ||
+ e.Name == W.tbl);
+
+ if (element.Name == W.tbl)
+ return element
+ .DescendantsTrimmed(W.tr)
+ .Where(e => e.Name == W.tr);
+
+ if (element.Name == W.tr)
+ return element
+ .DescendantsTrimmed(W.tc)
+ .Where(e => e.Name == W.tc);
+
+ if (element.Name == W.p)
+ return element
+ .DescendantsTrimmed(e => e.Name == W.r ||
+ e.Name == W.pict ||
+ e.Name == W.drawing)
+ .Where(e => e.Name == W.r ||
+ e.Name == W.pict ||
+ e.Name == W.drawing);
+
+ if (element.Name == W.r)
+ return element
+ .DescendantsTrimmed(e => W.SubRunLevelContent.Contains(e.Name))
+ .Where(e => W.SubRunLevelContent.Contains(e.Name));
+
+ if (element.Name == MC.AlternateContent)
+ return element
+ .DescendantsTrimmed(e =>
+ e.Name == W.pict ||
+ e.Name == W.drawing ||
+ e.Name == MC.Fallback)
+ .Where(e =>
+ e.Name == W.pict ||
+ e.Name == W.drawing);
+
+ if (element.Name == W.pict || element.Name == W.drawing)
+ return element
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.txbxContent);
+
+ return XElement.EmptySequence;
+ }
+
+ public static IEnumerable<XElement> LogicalChildrenContent(this IEnumerable<XElement> source)
+ {
+ foreach (XElement e1 in source)
+ foreach (XElement e2 in e1.LogicalChildrenContent())
+ yield return e2;
+ }
+
+ public static IEnumerable<XElement> LogicalChildrenContent(this XElement element, XName name)
+ {
+ return element.LogicalChildrenContent().Where(e => e.Name == name);
+ }
+
+ public static IEnumerable<XElement> LogicalChildrenContent(this IEnumerable<XElement> source, XName name)
+ {
+ foreach (XElement e1 in source)
+ foreach (XElement e2 in e1.LogicalChildrenContent(name))
+ yield return e2;
+ }
+
+ public static IEnumerable<OpenXmlPart> ContentParts(this WordprocessingDocument doc)
+ {
+ yield return doc.MainDocumentPart;
+
+ foreach (var hdr in doc.MainDocumentPart.HeaderParts)
+ yield return hdr;
+
+ foreach (var ftr in doc.MainDocumentPart.FooterParts)
+ yield return ftr;
+
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ yield return doc.MainDocumentPart.FootnotesPart;
+
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ yield return doc.MainDocumentPart.EndnotesPart;
+ }
+
+ /// <summary>
+ /// Creates a complete list of all parts contained in the <see cref="OpenXmlPartContainer"/>.
+ /// </summary>
+ /// <param name="container">
+ /// A <see cref="WordprocessingDocument"/>, <see cref="SpreadsheetDocument"/>, or
+ /// <see cref="PresentationDocument"/>.
+ /// </param>
+ /// <returns>list of <see cref="OpenXmlPart"/>s contained in the <see cref="OpenXmlPartContainer"/>.</returns>
+ public static List<OpenXmlPart> GetAllParts(this OpenXmlPartContainer container)
+ {
+ // Use a HashSet so that parts are processed only once.
+ HashSet<OpenXmlPart> partList = new HashSet<OpenXmlPart>();
+
+ foreach (IdPartPair p in container.Parts)
+ AddPart(partList, p.OpenXmlPart);
+
+ return partList.OrderBy(p => p.ContentType).ThenBy(p => p.Uri.ToString()).ToList();
+ }
+
+ private static void AddPart(HashSet<OpenXmlPart> partList, OpenXmlPart part)
+ {
+ if (partList.Contains(part)) return;
+
+ partList.Add(part);
+ foreach (IdPartPair p in part.Parts)
+ AddPart(partList, p.OpenXmlPart);
+ }
+ }
+
+ public static class FlatOpc
+ {
+ private class FlatOpcTupple
+ {
+ public char FoCharacter;
+ public int FoChunk;
+ }
+
+ private static XElement GetContentsAsXml(PackagePart part)
+ {
+ XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage";
+
+ if (part.ContentType.EndsWith("xml"))
+ {
+ using (Stream str = part.GetStream())
+ using (StreamReader streamReader = new StreamReader(str))
+ using (XmlReader xr = XmlReader.Create(streamReader))
+ return new XElement(pkg + "part",
+ new XAttribute(pkg + "name", part.Uri),
+ new XAttribute(pkg + "contentType", part.ContentType),
+ new XElement(pkg + "xmlData",
+ XElement.Load(xr)
+ )
+ );
+ }
+ else
+ {
+ using (Stream str = part.GetStream())
+ using (BinaryReader binaryReader = new BinaryReader(str))
+ {
+ int len = (int)binaryReader.BaseStream.Length;
+ byte[] byteArray = binaryReader.ReadBytes(len);
+ // the following expression creates the base64String, then chunks
+ // it to lines of 76 characters long
+ string base64String = (System.Convert.ToBase64String(byteArray))
+ .Select
+ (
+ (c, i) => new FlatOpcTupple()
+ {
+ FoCharacter = c,
+ FoChunk = i / 76
+ }
+ )
+ .GroupBy(c => c.FoChunk)
+ .Aggregate(
+ new StringBuilder(),
+ (s, i) =>
+ s.Append(
+ i.Aggregate(
+ new StringBuilder(),
+ (seed, it) => seed.Append(it.FoCharacter),
+ sb => sb.ToString()
+ )
+ )
+ .Append(Environment.NewLine),
+ s => s.ToString()
+ );
+ return new XElement(pkg + "part",
+ new XAttribute(pkg + "name", part.Uri),
+ new XAttribute(pkg + "contentType", part.ContentType),
+ new XAttribute(pkg + "compression", "store"),
+ new XElement(pkg + "binaryData", base64String)
+ );
+ }
+ }
+ }
+
+ private static XProcessingInstruction GetProcessingInstruction(string path)
+ {
+ var fi = new FileInfo(path);
+ if (Util.IsWordprocessingML(fi.Extension))
+ return new XProcessingInstruction("mso-application",
+ "progid=\"Word.Document\"");
+ if (Util.IsPresentationML(fi.Extension))
+ return new XProcessingInstruction("mso-application",
+ "progid=\"PowerPoint.Show\"");
+ if (Util.IsSpreadsheetML(fi.Extension))
+ return new XProcessingInstruction("mso-application",
+ "progid=\"Excel.Sheet\"");
+ return null;
+ }
+
+ public static XmlDocument OpcToXmlDocument(string fileName)
+ {
+ using (Package package = Package.Open(fileName))
+ {
+ XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage";
+
+ XDeclaration declaration = new XDeclaration("1.0", "UTF-8", "yes");
+ XDocument doc = new XDocument(
+ declaration,
+ GetProcessingInstruction(fileName),
+ new XElement(pkg + "package",
+ new XAttribute(XNamespace.Xmlns + "pkg", pkg.ToString()),
+ package.GetParts().Select(part => GetContentsAsXml(part))
+ )
+ );
+ return GetXmlDocument(doc);
+ }
+ }
+
+ public static XDocument OpcToXDocument(string fileName)
+ {
+ using (Package package = Package.Open(fileName))
+ {
+ XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage";
+
+ XDeclaration declaration = new XDeclaration("1.0", "UTF-8", "yes");
+ XDocument doc = new XDocument(
+ declaration,
+ GetProcessingInstruction(fileName),
+ new XElement(pkg + "package",
+ new XAttribute(XNamespace.Xmlns + "pkg", pkg.ToString()),
+ package.GetParts().Select(part => GetContentsAsXml(part))
+ )
+ );
+ return doc;
+ }
+ }
+
+ public static string[] OpcToText(string fileName)
+ {
+ using (Package package = Package.Open(fileName))
+ {
+ XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage";
+
+ XDeclaration declaration = new XDeclaration("1.0", "UTF-8", "yes");
+ XDocument doc = new XDocument(
+ declaration,
+ GetProcessingInstruction(fileName),
+ new XElement(pkg + "package",
+ new XAttribute(XNamespace.Xmlns + "pkg", pkg.ToString()),
+ package.GetParts().Select(part => GetContentsAsXml(part))
+ )
+ );
+ return doc.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);
+ }
+ }
+
+ private static XmlDocument GetXmlDocument(XDocument document)
+ {
+ using (XmlReader xmlReader = document.CreateReader())
+ {
+ XmlDocument xmlDoc = new XmlDocument();
+ xmlDoc.Load(xmlReader);
+ if (document.Declaration != null)
+ {
+ XmlDeclaration dec = xmlDoc.CreateXmlDeclaration(document.Declaration.Version,
+ document.Declaration.Encoding, document.Declaration.Standalone);
+ xmlDoc.InsertBefore(dec, xmlDoc.FirstChild);
+ }
+ return xmlDoc;
+ }
+ }
+
+ private static XDocument GetXDocument(this XmlDocument document)
+ {
+ XDocument xDoc = new XDocument();
+ using (XmlWriter xmlWriter = xDoc.CreateWriter())
+ document.WriteTo(xmlWriter);
+ XmlDeclaration decl =
+ document.ChildNodes.OfType<XmlDeclaration>().FirstOrDefault();
+ if (decl != null)
+ xDoc.Declaration = new XDeclaration(decl.Version, decl.Encoding,
+ decl.Standalone);
+ return xDoc;
+ }
+
+ public static void FlatToOpc(XmlDocument doc, string outputPath)
+ {
+ XDocument xd = GetXDocument(doc);
+ FlatToOpc(xd, outputPath);
+ }
+
+ public static void FlatToOpc(string xmlText, string outputPath)
+ {
+ XDocument xd = XDocument.Parse(xmlText);
+ FlatToOpc(xd, outputPath);
+ }
+
+ public static void FlatToOpc(XDocument doc, string outputPath)
+ {
+ XNamespace pkg =
+ "http://schemas.microsoft.com/office/2006/xmlPackage";
+ XNamespace rel =
+ "http://schemas.openxmlformats.org/package/2006/relationships";
+
+ using (Package package = Package.Open(outputPath, FileMode.Create))
+ {
+ // add all parts (but not relationships)
+ foreach (var xmlPart in doc.Root
+ .Elements()
+ .Where(p =>
+ (string)p.Attribute(pkg + "contentType") !=
+ "application/vnd.openxmlformats-package.relationships+xml"))
+ {
+ string name = (string)xmlPart.Attribute(pkg + "name");
+ string contentType = (string)xmlPart.Attribute(pkg + "contentType");
+ if (contentType.EndsWith("xml"))
+ {
+ Uri u = new Uri(name, UriKind.Relative);
+ PackagePart part = package.CreatePart(u, contentType,
+ CompressionOption.SuperFast);
+ using (Stream str = part.GetStream(FileMode.Create))
+ using (XmlWriter xmlWriter = XmlWriter.Create(str))
+ xmlPart.Element(pkg + "xmlData")
+ .Elements()
+ .First()
+ .WriteTo(xmlWriter);
+ }
+ else
+ {
+ Uri u = new Uri(name, UriKind.Relative);
+ PackagePart part = package.CreatePart(u, contentType,
+ CompressionOption.SuperFast);
+ using (Stream str = part.GetStream(FileMode.Create))
+ using (BinaryWriter binaryWriter = new BinaryWriter(str))
+ {
+ string base64StringInChunks =
+ (string)xmlPart.Element(pkg + "binaryData");
+ char[] base64CharArray = base64StringInChunks
+ .Where(c => c != '\r' && c != '\n').ToArray();
+ byte[] byteArray =
+ System.Convert.FromBase64CharArray(base64CharArray,
+ 0, base64CharArray.Length);
+ binaryWriter.Write(byteArray);
+ }
+ }
+ }
+
+ foreach (var xmlPart in doc.Root.Elements())
+ {
+ string name = (string)xmlPart.Attribute(pkg + "name");
+ string contentType = (string)xmlPart.Attribute(pkg + "contentType");
+ if (contentType ==
+ "application/vnd.openxmlformats-package.relationships+xml")
+ {
+ // add the package level relationships
+ if (name == "/_rels/.rels")
+ {
+ foreach (XElement xmlRel in
+ xmlPart.Descendants(rel + "Relationship"))
+ {
+ string id = (string)xmlRel.Attribute("Id");
+ string type = (string)xmlRel.Attribute("Type");
+ string target = (string)xmlRel.Attribute("Target");
+ string targetMode =
+ (string)xmlRel.Attribute("TargetMode");
+ if (targetMode == "External")
+ package.CreateRelationship(
+ new Uri(target, UriKind.Absolute),
+ TargetMode.External, type, id);
+ else
+ package.CreateRelationship(
+ new Uri(target, UriKind.Relative),
+ TargetMode.Internal, type, id);
+ }
+ }
+ else
+ // add part level relationships
+ {
+ string directory = name.Substring(0, name.IndexOf("/_rels"));
+ string relsFilename = name.Substring(name.LastIndexOf('/'));
+ string filename =
+ relsFilename.Substring(0, relsFilename.IndexOf(".rels"));
+ PackagePart fromPart = package.GetPart(
+ new Uri(directory + filename, UriKind.Relative));
+ foreach (XElement xmlRel in
+ xmlPart.Descendants(rel + "Relationship"))
+ {
+ string id = (string)xmlRel.Attribute("Id");
+ string type = (string)xmlRel.Attribute("Type");
+ string target = (string)xmlRel.Attribute("Target");
+ string targetMode =
+ (string)xmlRel.Attribute("TargetMode");
+ if (targetMode == "External")
+ fromPart.CreateRelationship(
+ new Uri(target, UriKind.Absolute),
+ TargetMode.External, type, id);
+ else
+ fromPart.CreateRelationship(
+ new Uri(target, UriKind.Relative),
+ TargetMode.Internal, type, id);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public class Base64
+ {
+ public static string ConvertToBase64(string fileName)
+ {
+ byte[] ba = System.IO.File.ReadAllBytes(fileName);
+ string base64String = (System.Convert.ToBase64String(ba))
+ .Select
+ (
+ (c, i) => new
+ {
+ Chunk = i / 76,
+ Character = c
+ }
+ )
+ .GroupBy(c => c.Chunk)
+ .Aggregate(
+ new StringBuilder(),
+ (s, i) =>
+ s.Append(
+ i.Aggregate(
+ new StringBuilder(),
+ (seed, it) => seed.Append(it.Character),
+ sb => sb.ToString()
+ )
+ )
+ .Append(Environment.NewLine),
+ s =>
+ {
+ s.Length -= Environment.NewLine.Length;
+ return s.ToString();
+ }
+ );
+
+ return base64String;
+ }
+
+ public static byte[] ConvertFromBase64(string fileName, string b64)
+ {
+ string b64b = b64.Replace("\r\n", "");
+ byte[] ba = System.Convert.FromBase64String(b64b);
+ return ba;
+ }
+ }
+
+ public static class XmlUtil
+ {
+ public static XAttribute GetXmlSpaceAttribute(string value)
+ {
+ return (value.Length > 0) && ((value[0] == ' ') || (value[value.Length - 1] == ' '))
+ ? new XAttribute(XNamespace.Xml + "space", "preserve")
+ : null;
+ }
+
+ public static XAttribute GetXmlSpaceAttribute(char value)
+ {
+ return value == ' ' ? new XAttribute(XNamespace.Xml + "space", "preserve") : null;
+ }
+ }
+
+ public static class WordprocessingMLUtil
+ {
+ private static HashSet<string> UnknownFonts = new HashSet<string>();
+ private static HashSet<string> KnownFamilies = null;
+
+ public static int CalcWidthOfRunInTwips(XElement r)
+ {
+ if (KnownFamilies == null)
+ {
+ KnownFamilies = new HashSet<string>();
+ var families = FontFamily.Families;
+ foreach (var fam in families)
+ KnownFamilies.Add(fam.Name);
+ }
+
+ var fontName = (string)r.Attribute(PtOpenXml.pt + "FontName");
+ if (fontName == null)
+ fontName = (string)r.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
+ if (fontName == null)
+ throw new OpenXmlPowerToolsException("Internal Error, should have FontName attribute");
+ if (UnknownFonts.Contains(fontName))
+ return 0;
+
+ var rPr = r.Element(W.rPr);
+ if (rPr == null)
+ throw new OpenXmlPowerToolsException("Internal Error, should have run properties");
+ var languageType = (string)r.Attribute(PtOpenXml.LanguageType);
+ decimal? sz = null;
+ if (languageType == "bidi")
+ sz = (decimal?)rPr.Elements(W.szCs).Attributes(W.val).FirstOrDefault();
+ else
+ sz = (decimal?)rPr.Elements(W.sz).Attributes(W.val).FirstOrDefault();
+ if (sz == null)
+ sz = 22m;
+
+ // unknown font families will throw ArgumentException, in which case just return 0
+ if (!KnownFamilies.Contains(fontName))
+ return 0;
+ // in theory, all unknown fonts are found by the above test, but if not...
+ FontFamily ff;
+ try
+ {
+ ff = new FontFamily(fontName);
+ }
+ catch (ArgumentException)
+ {
+ UnknownFonts.Add(fontName);
+
+ return 0;
+ }
+ FontStyle fs = FontStyle.Regular;
+ var bold = GetBoolProp(rPr, W.b) || GetBoolProp(rPr, W.bCs);
+ var italic = GetBoolProp(rPr, W.i) || GetBoolProp(rPr, W.iCs);
+ if (bold && !italic)
+ fs = FontStyle.Bold;
+ if (italic && !bold)
+ fs = FontStyle.Italic;
+ if (bold && italic)
+ fs = FontStyle.Bold | FontStyle.Italic;
+
+ var runText = r.DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate();
+
+ var tabLength = r.DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.tab)
+ .Select(t => (decimal)t.Attribute(PtOpenXml.TabWidth))
+ .Sum();
+
+ if (runText.Length == 0 && tabLength == 0)
+ return 0;
+
+ int multiplier = 1;
+ if (runText.Length <= 2)
+ multiplier = 100;
+ else if (runText.Length <= 4)
+ multiplier = 50;
+ else if (runText.Length <= 8)
+ multiplier = 25;
+ else if (runText.Length <= 16)
+ multiplier = 12;
+ else if (runText.Length <= 32)
+ multiplier = 6;
+ if (multiplier != 1)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < multiplier; i++)
+ sb.Append(runText);
+ runText = sb.ToString();
+ }
+
+ try
+ {
+ using (Font f = new Font(ff, (float)sz / 2f, fs))
+ {
+ System.Windows.Forms.TextFormatFlags tff = System.Windows.Forms.TextFormatFlags.NoPadding;
+ Size proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = System.Windows.Forms.TextRenderer.MeasureText(runText, f, proposedSize, tff); // sf returns size in pixels
+ var dpi = 96m;
+ var twip = (int)(((sf.Width / dpi) * 1440m) / multiplier + tabLength * 1440m);
+ return twip;
+ }
+ }
+ catch (ArgumentException)
+ {
+ try
+ {
+ var fs2 = FontStyle.Regular;
+ using (Font f = new Font(ff, (float)sz / 2f, fs2))
+ {
+ System.Windows.Forms.TextFormatFlags tff = System.Windows.Forms.TextFormatFlags.NoPadding;
+ Size proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = System.Windows.Forms.TextRenderer.MeasureText(runText, f, proposedSize, tff); // sf returns size in pixels
+ var dpi = 96m;
+ var twip = (int)(((sf.Width / dpi) * 1440m) / multiplier + tabLength * 1440m);
+ return twip;
+ }
+ }
+ catch (ArgumentException)
+ {
+ var fs2 = FontStyle.Bold;
+ try
+ {
+ using (Font f = new Font(ff, (float)sz / 2f, fs2))
+ {
+ System.Windows.Forms.TextFormatFlags tff = System.Windows.Forms.TextFormatFlags.NoPadding;
+ Size proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = System.Windows.Forms.TextRenderer.MeasureText(runText, f, proposedSize, tff); // sf returns size in pixels
+ var dpi = 96m;
+ var twip = (int)(((sf.Width / dpi) * 1440m) / multiplier + tabLength * 1440m);
+ return twip;
+ }
+ }
+ catch (ArgumentException)
+ {
+ // if both regular and bold fail, then get metrics for Times New Roman
+ // use the original FontStyle (in fs)
+ FontFamily ff2;
+ ff2 = new FontFamily("Times New Roman");
+ using (Font f = new Font(ff2, (float)sz / 2f, fs))
+ {
+ System.Windows.Forms.TextFormatFlags tff = System.Windows.Forms.TextFormatFlags.NoPadding;
+ Size proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = System.Windows.Forms.TextRenderer.MeasureText(runText, f, proposedSize, tff); // sf returns size in pixels
+ var dpi = 96m;
+ var twip = (int)(((sf.Width / dpi) * 1440m) / multiplier + tabLength * 1440m);
+ return twip;
+ }
+ }
+ }
+ }
+ }
+
+ public static bool GetBoolProp(XElement runProps, XName xName)
+ {
+ var p = runProps.Element(xName);
+ if (p == null)
+ return false;
+ var v = p.Attribute(W.val);
+ if (v == null)
+ return true;
+ var s = v.Value.ToLower();
+ if (s == "0" || s == "false")
+ return false;
+ if (s == "1" || s == "true")
+ return true;
+ return false;
+ }
+
+ private static readonly List<XName> AdditionalRunContainerNames = new List<XName>
+ {
+ W.w + "bdo",
+ W.customXml,
+ W.dir,
+ W.fldSimple,
+ W.hyperlink,
+ W.moveFrom,
+ W.moveTo,
+ W.sdtContent
+ };
+
+ public static XElement CoalesceAdjacentRunsWithIdenticalFormatting(XElement runContainer)
+ {
+ const string dontConsolidate = "DontConsolidate";
+
+ IEnumerable<IGrouping<string, XElement>> groupedAdjacentRunsWithIdenticalFormatting =
+ runContainer
+ .Elements()
+ .GroupAdjacent(ce =>
+ {
+ if (ce.Name == W.r)
+ {
+ if (ce.Elements().Count(e => e.Name != W.rPr) != 1)
+ return dontConsolidate;
+
+ XElement rPr = ce.Element(W.rPr);
+ string rPrString = rPr != null ? rPr.ToString(SaveOptions.None) : string.Empty;
+
+ if (ce.Element(W.t) != null)
+ return "Wt" + rPrString;
+
+ if (ce.Element(W.instrText) != null)
+ return "WinstrText" + rPrString;
+
+ return dontConsolidate;
+ }
+
+ if (ce.Name == W.ins)
+ {
+ if (ce.Elements(W.del).Any())
+ {
+ return dontConsolidate;
+#if false
+ // for w:ins/w:del/w:r/w:delText
+ if ((ce.Elements(W.del).Elements(W.r).Elements().Count(e => e.Name != W.rPr) != 1) ||
+ !ce.Elements().Elements().Elements(W.delText).Any())
+ return dontConsolidate;
+
+ XAttribute dateIns = ce.Attribute(W.date);
+ XElement del = ce.Element(W.del);
+ XAttribute dateDel = del.Attribute(W.date);
+
+ string authorIns = (string) ce.Attribute(W.author) ?? string.Empty;
+ string dateInsString = dateIns != null
+ ? ((DateTime) dateIns).ToString("s")
+ : string.Empty;
+ string authorDel = (string) del.Attribute(W.author) ?? string.Empty;
+ string dateDelString = dateDel != null
+ ? ((DateTime) dateDel).ToString("s")
+ : string.Empty;
+
+ return "Wins" +
+ authorIns +
+ dateInsString +
+ authorDel +
+ dateDelString +
+ ce.Elements(W.del)
+ .Elements(W.r)
+ .Elements(W.rPr)
+ .Select(rPr => rPr.ToString(SaveOptions.None))
+ .StringConcatenate();
+#endif
+ }
+
+ // w:ins/w:r/w:t
+ if ((ce.Elements().Elements().Count(e => e.Name != W.rPr) != 1) ||
+ !ce.Elements().Elements(W.t).Any())
+ return dontConsolidate;
+
+ XAttribute dateIns2 = ce.Attribute(W.date);
+
+ string authorIns2 = (string) ce.Attribute(W.author) ?? string.Empty;
+ string dateInsString2 = dateIns2 != null
+ ? ((DateTime) dateIns2).ToString("s")
+ : string.Empty;
+
+ string idIns2 = (string)ce.Attribute(W.id);
+
+ return "Wins2" +
+ authorIns2 +
+ dateInsString2 +
+ idIns2 +
+ ce.Elements()
+ .Elements(W.rPr)
+ .Select(rPr => rPr.ToString(SaveOptions.None))
+ .StringConcatenate();
+ }
+
+ if (ce.Name == W.del)
+ {
+ if ((ce.Elements(W.r).Elements().Count(e => e.Name != W.rPr) != 1) ||
+ !ce.Elements().Elements(W.delText).Any())
+ return dontConsolidate;
+
+ XAttribute dateDel2 = ce.Attribute(W.date);
+
+ string authorDel2 = (string) ce.Attribute(W.author) ?? string.Empty;
+ string dateDelString2 = dateDel2 != null ? ((DateTime) dateDel2).ToString("s") : string.Empty;
+
+ return "Wdel" +
+ authorDel2 +
+ dateDelString2 +
+ ce.Elements(W.r)
+ .Elements(W.rPr)
+ .Select(rPr => rPr.ToString(SaveOptions.None))
+ .StringConcatenate();
+ }
+
+ return dontConsolidate;
+ });
+
+ var runContainerWithConsolidatedRuns = new XElement(runContainer.Name,
+ runContainer.Attributes(),
+ groupedAdjacentRunsWithIdenticalFormatting.Select(g =>
+ {
+ if (g.Key == dontConsolidate)
+ return (object) g;
+
+ string textValue = g
+ .Select(r =>
+ r.Descendants()
+ .Where(d => (d.Name == W.t) || (d.Name == W.delText) || (d.Name == W.instrText))
+ .Select(d => d.Value)
+ .StringConcatenate())
+ .StringConcatenate();
+ XAttribute xs = XmlUtil.GetXmlSpaceAttribute(textValue);
+
+ if (g.First().Name == W.r)
+ {
+ if (g.First().Element(W.t) != null)
+ {
+ IEnumerable<IEnumerable<XAttribute>> statusAtt =
+ g.Select(r => r.Descendants(W.t).Take(1).Attributes(PtOpenXml.Status));
+ return new XElement(W.r,
+ g.First().Elements(W.rPr),
+ new XElement(W.t, statusAtt, xs, textValue));
+ }
+
+ if (g.First().Element(W.instrText) != null)
+ return new XElement(W.r,
+ g.First().Elements(W.rPr),
+ new XElement(W.instrText, xs, textValue));
+ }
+
+ if (g.First().Name == W.ins)
+ {
+#if false
+ if (g.First().Elements(W.del).Any())
+ return new XElement(W.ins,
+ g.First().Attributes(),
+ new XElement(W.del,
+ g.First().Elements(W.del).Attributes(),
+ new XElement(W.r,
+ g.First().Elements(W.del).Elements(W.r).Elements(W.rPr),
+ new XElement(W.delText, xs, textValue))));
+#endif
+ return new XElement(W.ins,
+ g.First().Attributes(),
+ new XElement(W.r,
+ g.First().Elements(W.r).Elements(W.rPr),
+ new XElement(W.t, xs, textValue)));
+ }
+
+ if (g.First().Name == W.del)
+ return new XElement(W.del,
+ g.First().Attributes(),
+ new XElement(W.r,
+ g.First().Elements(W.r).Elements(W.rPr),
+ new XElement(W.delText, xs, textValue)));
+
+ return g;
+ }));
+
+ // Process w:txbxContent//w:p
+ foreach (XElement txbx in runContainerWithConsolidatedRuns.Descendants(W.txbxContent))
+ foreach (XElement txbxPara in txbx.DescendantsTrimmed(W.txbxContent).Where(d => d.Name == W.p))
+ {
+ XElement newPara = CoalesceAdjacentRunsWithIdenticalFormatting(txbxPara);
+ txbxPara.ReplaceWith(newPara);
+ }
+
+ // Process additional run containers.
+ List<XElement> runContainers = runContainerWithConsolidatedRuns
+ .Descendants()
+ .Where(d => AdditionalRunContainerNames.Contains(d.Name))
+ .ToList();
+ foreach (XElement container in runContainers)
+ {
+ XElement newContainer = CoalesceAdjacentRunsWithIdenticalFormatting(container);
+ container.ReplaceWith(newContainer);
+ }
+
+ return runContainerWithConsolidatedRuns;
+ }
+
+ private static Dictionary<XName, int> Order_settings = new Dictionary<XName, int>
+ {
+ { W.writeProtection, 10},
+ { W.view, 20},
+ { W.zoom, 30},
+ { W.removePersonalInformation, 40},
+ { W.removeDateAndTime, 50},
+ { W.doNotDisplayPageBoundaries, 60},
+ { W.displayBackgroundShape, 70},
+ { W.printPostScriptOverText, 80},
+ { W.printFractionalCharacterWidth, 90},
+ { W.printFormsData, 100},
+ { W.embedTrueTypeFonts, 110},
+ { W.embedSystemFonts, 120},
+ { W.saveSubsetFonts, 130},
+ { W.saveFormsData, 140},
+ { W.mirrorMargins, 150},
+ { W.alignBordersAndEdges, 160},
+ { W.bordersDoNotSurroundHeader, 170},
+ { W.bordersDoNotSurroundFooter, 180},
+ { W.gutterAtTop, 190},
+ { W.hideSpellingErrors, 200},
+ { W.hideGrammaticalErrors, 210},
+ { W.activeWritingStyle, 220},
+ { W.proofState, 230},
+ { W.formsDesign, 240},
+ { W.attachedTemplate, 250},
+ { W.linkStyles, 260},
+ { W.stylePaneFormatFilter, 270},
+ { W.stylePaneSortMethod, 280},
+ { W.documentType, 290},
+ { W.mailMerge, 300},
+ { W.revisionView, 310},
+ { W.trackRevisions, 320},
+ { W.doNotTrackMoves, 330},
+ { W.doNotTrackFormatting, 340},
+ { W.documentProtection, 350},
+ { W.autoFormatOverride, 360},
+ { W.styleLockTheme, 370},
+ { W.styleLockQFSet, 380},
+ { W.defaultTabStop, 390},
+ { W.autoHyphenation, 400},
+ { W.consecutiveHyphenLimit, 410},
+ { W.hyphenationZone, 420},
+ { W.doNotHyphenateCaps, 430},
+ { W.showEnvelope, 440},
+ { W.summaryLength, 450},
+ { W.clickAndTypeStyle, 460},
+ { W.defaultTableStyle, 470},
+ { W.evenAndOddHeaders, 480},
+ { W.bookFoldRevPrinting, 490},
+ { W.bookFoldPrinting, 500},
+ { W.bookFoldPrintingSheets, 510},
+ { W.drawingGridHorizontalSpacing, 520},
+ { W.drawingGridVerticalSpacing, 530},
+ { W.displayHorizontalDrawingGridEvery, 540},
+ { W.displayVerticalDrawingGridEvery, 550},
+ { W.doNotUseMarginsForDrawingGridOrigin, 560},
+ { W.drawingGridHorizontalOrigin, 570},
+ { W.drawingGridVerticalOrigin, 580},
+ { W.doNotShadeFormData, 590},
+ { W.noPunctuationKerning, 600},
+ { W.characterSpacingControl, 610},
+ { W.printTwoOnOne, 620},
+ { W.strictFirstAndLastChars, 630},
+ { W.noLineBreaksAfter, 640},
+ { W.noLineBreaksBefore, 650},
+ { W.savePreviewPicture, 660},
+ { W.doNotValidateAgainstSchema, 670},
+ { W.saveInvalidXml, 680},
+ { W.ignoreMixedContent, 690},
+ { W.alwaysShowPlaceholderText, 700},
+ { W.doNotDemarcateInvalidXml, 710},
+ { W.saveXmlDataOnly, 720},
+ { W.useXSLTWhenSaving, 730},
+ { W.saveThroughXslt, 740},
+ { W.showXMLTags, 750},
+ { W.alwaysMergeEmptyNamespace, 760},
+ { W.updateFields, 770},
+ { W.footnotePr, 780},
+ { W.endnotePr, 790},
+ { W.compat, 800},
+ { W.docVars, 810},
+ { W.rsids, 820},
+ { M.mathPr, 830},
+ { W.attachedSchema, 840},
+ { W.themeFontLang, 850},
+ { W.clrSchemeMapping, 860},
+ { W.doNotIncludeSubdocsInStats, 870},
+ { W.doNotAutoCompressPictures, 880},
+ { W.forceUpgrade, 890},
+ //{W.captions, 900},
+ { W.readModeInkLockDown, 910},
+ { W.smartTagType, 920},
+ //{W.sl:schemaLibrary, 930},
+ { W.doNotEmbedSmartTags, 940},
+ { W.decimalSymbol, 950},
+ { W.listSeparator, 960},
+ };
+
+#if false
+// from the schema in the standard
+
+writeProtection
+view
+zoom
+removePersonalInformation
+removeDateAndTime
+doNotDisplayPageBoundaries
+displayBackgroundShape
+printPostScriptOverText
+printFractionalCharacterWidth
+printFormsData
+embedTrueTypeFonts
+embedSystemFonts
+saveSubsetFonts
+saveFormsData
+mirrorMargins
+alignBordersAndEdges
+bordersDoNotSurroundHeader
+bordersDoNotSurroundFooter
+gutterAtTop
+hideSpellingErrors
+hideGrammaticalErrors
+activeWritingStyle
+proofState
+formsDesign
+attachedTemplate
+linkStyles
+stylePaneFormatFilter
+stylePaneSortMethod
+documentType
+mailMerge
+revisionView
+trackRevisions
+doNotTrackMoves
+doNotTrackFormatting
+documentProtection
+autoFormatOverride
+styleLockTheme
+styleLockQFSet
+defaultTabStop
+autoHyphenation
+consecutiveHyphenLimit
+hyphenationZone
+doNotHyphenateCaps
+showEnvelope
+summaryLength
+clickAndTypeStyle
+defaultTableStyle
+evenAndOddHeaders
+bookFoldRevPrinting
+bookFoldPrinting
+bookFoldPrintingSheets
+drawingGridHorizontalSpacing
+drawingGridVerticalSpacing
+displayHorizontalDrawingGridEvery
+displayVerticalDrawingGridEvery
+doNotUseMarginsForDrawingGridOrigin
+drawingGridHorizontalOrigin
+drawingGridVerticalOrigin
+doNotShadeFormData
+noPunctuationKerning
+characterSpacingControl
+printTwoOnOne
+strictFirstAndLastChars
+noLineBreaksAfter
+noLineBreaksBefore
+savePreviewPicture
+doNotValidateAgainstSchema
+saveInvalidXml
+ignoreMixedContent
+alwaysShowPlaceholderText
+doNotDemarcateInvalidXml
+saveXmlDataOnly
+useXSLTWhenSaving
+saveThroughXslt
+showXMLTags
+alwaysMergeEmptyNamespace
+updateFields
+footnotePr
+endnotePr
+compat
+docVars
+rsids
+m:mathPr
+attachedSchema
+themeFontLang
+clrSchemeMapping
+doNotIncludeSubdocsInStats
+doNotAutoCompressPictures
+forceUpgrade
+captions
+readModeInkLockDown
+smartTagType
+sl:schemaLibrary
+doNotEmbedSmartTags
+decimalSymbol
+listSeparator
+#endif
+
+ private static Dictionary<XName, int> Order_pPr = new Dictionary<XName, int>
+ {
+ { W.pStyle, 10 },
+ { W.keepNext, 20 },
+ { W.keepLines, 30 },
+ { W.pageBreakBefore, 40 },
+ { W.framePr, 50 },
+ { W.widowControl, 60 },
+ { W.numPr, 70 },
+ { W.suppressLineNumbers, 80 },
+ { W.pBdr, 90 },
+ { W.shd, 100 },
+ { W.tabs, 120 },
+ { W.suppressAutoHyphens, 130 },
+ { W.kinsoku, 140 },
+ { W.wordWrap, 150 },
+ { W.overflowPunct, 160 },
+ { W.topLinePunct, 170 },
+ { W.autoSpaceDE, 180 },
+ { W.autoSpaceDN, 190 },
+ { W.bidi, 200 },
+ { W.adjustRightInd, 210 },
+ { W.snapToGrid, 220 },
+ { W.spacing, 230 },
+ { W.ind, 240 },
+ { W.contextualSpacing, 250 },
+ { W.mirrorIndents, 260 },
+ { W.suppressOverlap, 270 },
+ { W.jc, 280 },
+ { W.textDirection, 290 },
+ { W.textAlignment, 300 },
+ { W.textboxTightWrap, 310 },
+ { W.outlineLvl, 320 },
+ { W.divId, 330 },
+ { W.cnfStyle, 340 },
+ { W.rPr, 350 },
+ { W.sectPr, 360 },
+ { W.pPrChange, 370 },
+ };
+
+ private static Dictionary<XName, int> Order_rPr = new Dictionary<XName, int>
+ {
+ { W.ins, 10 },
+ { W.del, 20 },
+ { W.rStyle, 30 },
+ { W.rFonts, 40 },
+ { W.b, 50 },
+ { W.bCs, 60 },
+ { W.i, 70 },
+ { W.iCs, 80 },
+ { W.caps, 90 },
+ { W.smallCaps, 100 },
+ { W.strike, 110 },
+ { W.dstrike, 120 },
+ { W.outline, 130 },
+ { W.shadow, 140 },
+ { W.emboss, 150 },
+ { W.imprint, 160 },
+ { W.noProof, 170 },
+ { W.snapToGrid, 180 },
+ { W.vanish, 190 },
+ { W.webHidden, 200 },
+ { W.color, 210 },
+ { W.spacing, 220 },
+ { W._w, 230 },
+ { W.kern, 240 },
+ { W.position, 250 },
+ { W.sz, 260 },
+ { W14.wShadow, 270 },
+ { W14.wTextOutline, 280 },
+ { W14.wTextFill, 290 },
+ { W14.wScene3d, 300 },
+ { W14.wProps3d, 310 },
+ { W.szCs, 320 },
+ { W.highlight, 330 },
+ { W.u, 340 },
+ { W.effect, 350 },
+ { W.bdr, 360 },
+ { W.shd, 370 },
+ { W.fitText, 380 },
+ { W.vertAlign, 390 },
+ { W.rtl, 400 },
+ { W.cs, 410 },
+ { W.em, 420 },
+ { W.lang, 430 },
+ { W.eastAsianLayout, 440 },
+ { W.specVanish, 450 },
+ { W.oMath, 460 },
+ };
+
+ private static Dictionary<XName, int> Order_tblPr = new Dictionary<XName, int>
+ {
+ { W.tblStyle, 10 },
+ { W.tblpPr, 20 },
+ { W.tblOverlap, 30 },
+ { W.bidiVisual, 40 },
+ { W.tblStyleRowBandSize, 50 },
+ { W.tblStyleColBandSize, 60 },
+ { W.tblW, 70 },
+ { W.jc, 80 },
+ { W.tblCellSpacing, 90 },
+ { W.tblInd, 100 },
+ { W.tblBorders, 110 },
+ { W.shd, 120 },
+ { W.tblLayout, 130 },
+ { W.tblCellMar, 140 },
+ { W.tblLook, 150 },
+ { W.tblCaption, 160 },
+ { W.tblDescription, 170 },
+ };
+
+ private static Dictionary<XName, int> Order_tblBorders = new Dictionary<XName, int>
+ {
+ { W.top, 10 },
+ { W.left, 20 },
+ { W.start, 30 },
+ { W.bottom, 40 },
+ { W.right, 50 },
+ { W.end, 60 },
+ { W.insideH, 70 },
+ { W.insideV, 80 },
+ };
+
+ private static Dictionary<XName, int> Order_tcPr = new Dictionary<XName, int>
+ {
+ { W.cnfStyle, 10 },
+ { W.tcW, 20 },
+ { W.gridSpan, 30 },
+ { W.hMerge, 40 },
+ { W.vMerge, 50 },
+ { W.tcBorders, 60 },
+ { W.shd, 70 },
+ { W.noWrap, 80 },
+ { W.tcMar, 90 },
+ { W.textDirection, 100 },
+ { W.tcFitText, 110 },
+ { W.vAlign, 120 },
+ { W.hideMark, 130 },
+ { W.headers, 140 },
+ };
+
+ private static Dictionary<XName, int> Order_tcBorders = new Dictionary<XName, int>
+ {
+ { W.top, 10 },
+ { W.start, 20 },
+ { W.left, 30 },
+ { W.bottom, 40 },
+ { W.right, 50 },
+ { W.end, 60 },
+ { W.insideH, 70 },
+ { W.insideV, 80 },
+ { W.tl2br, 90 },
+ { W.tr2bl, 100 },
+ };
+
+ private static Dictionary<XName, int> Order_pBdr = new Dictionary<XName, int>
+ {
+ { W.top, 10 },
+ { W.left, 20 },
+ { W.bottom, 30 },
+ { W.right, 40 },
+ { W.between, 50 },
+ { W.bar, 60 },
+ };
+
+ public static object WmlOrderElementsPerStandard(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.pPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_pPr.ContainsKey(e.Name))
+ return Order_pPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.rPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_rPr.ContainsKey(e.Name))
+ return Order_rPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tblPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_tblPr.ContainsKey(e.Name))
+ return Order_tblPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tcPr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_tcPr.ContainsKey(e.Name))
+ return Order_tcPr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tcBorders)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_tcBorders.ContainsKey(e.Name))
+ return Order_tcBorders[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.tblBorders)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_tblBorders.ContainsKey(e.Name))
+ return Order_tblBorders[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.pBdr)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_pBdr.ContainsKey(e.Name))
+ return Order_pBdr[e.Name];
+ return 999;
+ }));
+
+ if (element.Name == W.p)
+ {
+ var newP = new XElement(element.Name,
+ element.Attributes(),
+ element.Elements(W.pPr).Select(e => (XElement)WmlOrderElementsPerStandard(e)),
+ element.Elements().Where(e => e.Name != W.pPr).Select(e => (XElement)WmlOrderElementsPerStandard(e)));
+ return newP;
+ }
+
+ if (element.Name == W.r)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements(W.rPr).Select(e => (XElement)WmlOrderElementsPerStandard(e)),
+ element.Elements().Where(e => e.Name != W.rPr).Select(e => (XElement)WmlOrderElementsPerStandard(e)));
+
+ if (element.Name == W.settings)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => (XElement)WmlOrderElementsPerStandard(e)).OrderBy(e =>
+ {
+ if (Order_settings.ContainsKey(e.Name))
+ return Order_settings[e.Name];
+ return 999;
+ }));
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => WmlOrderElementsPerStandard(n)));
+ }
+ return node;
+ }
+
+ public static WmlDocument BreakLinkToTemplate(WmlDocument source)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(source.DocumentByteArray, 0, source.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ var efpp = wDoc.ExtendedFilePropertiesPart;
+ if (efpp != null)
+ {
+ var xd = efpp.GetXDocument();
+ var template = xd.Descendants(EP.Template).FirstOrDefault();
+ if (template != null)
+ template.Value = "";
+ efpp.PutXDocument();
+ }
+ }
+ var result = new WmlDocument(source.FileName, ms.ToArray());
+ return result;
+ }
+ }
+ }
+
+ public static class PresentationMLUtil
+ {
+ public static void FixUpPresentationDocument(PresentationDocument pDoc)
+ {
+ foreach (var part in pDoc.GetAllParts())
+ {
+ if (part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.theme+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml" ||
+ part.ContentType == "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" ||
+ part.ContentType == "application/vnd.ms-office.drawingml.diagramDrawing+xml")
+ {
+ XDocument xd = part.GetXDocument();
+ xd.Descendants().Attributes("smtClean").Remove();
+ xd.Descendants().Attributes("smtId").Remove();
+ part.PutXDocument();
+ }
+ if (part.ContentType == "application/vnd.openxmlformats-officedocument.vmlDrawing")
+ {
+ string fixedContent = null;
+
+ using (var stream = part.GetStream(FileMode.Open, FileAccess.ReadWrite))
+ {
+ using (var sr = new StreamReader(stream))
+ {
+ //string input = @" <![if gte mso 9]><v:imagedata o:relid=""rId15""";
+ var input = sr.ReadToEnd();
+ string pattern = @"<!\[(?<test>.*)\]>";
+ //string replacement = "<![CDATA[${test}]]>";
+ //fixedContent = Regex.Replace(input, pattern, replacement, RegexOptions.Multiline);
+ fixedContent = Regex.Replace(input, pattern, m =>
+ {
+ var g = m.Groups[1].Value;
+ if (g.StartsWith("CDATA["))
+ return "<![" + g + "]>";
+ else
+ return "<![CDATA[" + g + "]]>";
+ },
+ RegexOptions.Multiline);
+
+ //var input = @"xxxxx o:relid=""rId1"" o:relid=""rId1"" xxxxx";
+ pattern = @"o:relid=[""'](?<id1>.*)[""'] o:relid=[""'](?<id2>.*)[""']";
+ fixedContent = Regex.Replace(fixedContent, pattern, m =>
+ {
+ var g = m.Groups[1].Value;
+ return @"o:relid=""" + g + @"""";
+ },
+ RegexOptions.Multiline);
+
+ fixedContent = fixedContent.Replace("</xml>ml>", "</xml>");
+
+ stream.SetLength(fixedContent.Length);
+ }
+ }
+ using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(fixedContent)))
+ part.FeedData(ms);
+ }
+ }
+ }
+ }
+
+ public static class SpreadsheetMLUtil
+ {
+ public static string GetCellType(string value)
+ {
+ if (value.Any(c => !Char.IsDigit(c) && c != '.'))
+ return "str";
+ return null;
+ }
+
+ public static string IntToColumnId(int i)
+ {
+ if (i >= 0 && i <= 25)
+ return ((char)(((int)'A') + i)).ToString();
+ if (i >= 26 && i <= 701)
+ {
+ int v = i - 26;
+ int h = v / 26;
+ int l = v % 26;
+ return ((char)(((int)'A') + h)).ToString() + ((char)(((int)'A') + l)).ToString();
+ }
+ // 17576
+ if (i >= 702 && i <= 18277)
+ {
+ int v = i - 702;
+ int h = v / 676;
+ int r = v % 676;
+ int m = r / 26;
+ int l = r % 26;
+ return ((char)(((int)'A') + h)).ToString() +
+ ((char)(((int)'A') + m)).ToString() +
+ ((char)(((int)'A') + l)).ToString();
+ }
+ throw new ColumnReferenceOutOfRange(i.ToString());
+ }
+
+ private static int CharToInt(char c)
+ {
+ return (int)c - (int)'A';
+ }
+
+ public static int ColumnIdToInt(string cid)
+ {
+ if (cid.Length == 1)
+ return CharToInt(cid[0]);
+ if (cid.Length == 2)
+ {
+ return CharToInt(cid[0]) * 26 + CharToInt(cid[1]) + 26;
+ }
+ if (cid.Length == 3)
+ {
+
+ return CharToInt(cid[0]) * 676 + CharToInt(cid[1]) * 26 + CharToInt(cid[2]) + 702;
+ }
+ throw new ColumnReferenceOutOfRange(cid);
+ }
+
+ public static IEnumerable<string> ColumnIDs()
+ {
+ for (var c = (int)'A'; c <= (int)'Z'; ++c)
+ yield return ((char)c).ToString();
+ for (var c1 = (int)'A'; c1 <= (int)'Z'; ++c1)
+ for (var c2 = (int)'A'; c2 <= (int)'Z'; ++c2)
+ yield return ((char)c1).ToString() + ((char)c2).ToString();
+ for (var d1 = (int)'A'; d1 <= (int)'Z'; ++d1)
+ for (var d2 = (int)'A'; d2 <= (int)'Z'; ++d2)
+ for (var d3 = (int)'A'; d3 <= (int)'Z'; ++d3)
+ yield return ((char)d1).ToString() + ((char)d2).ToString() + ((char)d3).ToString();
+ }
+
+ public static string ColumnIdOf(string cellReference)
+ {
+ string columnIdOf = cellReference.Split('0', '1', '2', '3', '4', '5', '6', '7', '8', '9').First();
+ return columnIdOf;
+ }
+ }
+
+ public class Util
+ {
+ public static string[] WordprocessingExtensions = new[] {
+ ".docx",
+ ".docm",
+ ".dotx",
+ ".dotm",
+ };
+
+ public static bool IsWordprocessingML(string ext)
+ {
+ return WordprocessingExtensions.Contains(ext.ToLower());
+ }
+
+ public static string[] SpreadsheetExtensions = new[] {
+ ".xlsx",
+ ".xlsm",
+ ".xltx",
+ ".xltm",
+ ".xlam",
+ };
+
+ public static bool IsSpreadsheetML(string ext)
+ {
+ return SpreadsheetExtensions.Contains(ext.ToLower());
+ }
+
+ public static string[] PresentationExtensions = new[] {
+ ".pptx",
+ ".potx",
+ ".ppsx",
+ ".pptm",
+ ".potm",
+ ".ppsm",
+ ".ppam",
+ };
+
+ public static bool IsPresentationML(string ext)
+ {
+ return PresentationExtensions.Contains(ext.ToLower());
+ }
+
+ public static bool? GetBoolProp(XElement rPr, XName propertyName)
+ {
+ XElement propAtt = rPr.Element(propertyName);
+ if (propAtt == null)
+ return null;
+
+ XAttribute val = propAtt.Attribute(W.val);
+ if (val == null)
+ return true;
+
+ string s = ((string) val).ToLower();
+ if (s == "1")
+ return true;
+ if (s == "0")
+ return false;
+ if (s == "true")
+ return true;
+ if (s == "false")
+ return false;
+ if (s == "on")
+ return true;
+ if (s == "off")
+ return false;
+
+ return (bool) propAtt.Attribute(W.val);
+ }
+ }
+
+ public class FieldInfo
+ {
+ public string FieldType;
+ public string[] Switches;
+ public string[] Arguments;
+ }
+
+ public static class FieldParser
+ {
+ private enum State
+ {
+ InToken,
+ InWhiteSpace,
+ InQuotedToken,
+ OnOpeningQuote,
+ OnClosingQuote,
+ OnBackslash,
+ }
+
+ private static string[] GetTokens(string field)
+ {
+ State state = State.InWhiteSpace;
+ int tokenStart = 0;
+ char quoteStart = char.MinValue;
+ List<string> tokens = new List<string>();
+ for (int c = 0; c < field.Length; c++)
+ {
+ if (Char.IsWhiteSpace(field[c]))
+ {
+ if (state == State.InToken)
+ {
+ tokens.Add(field.Substring(tokenStart, c - tokenStart));
+ state = State.InWhiteSpace;
+ continue;
+ }
+ if (state == State.OnOpeningQuote)
+ {
+ tokenStart = c;
+ state = State.InQuotedToken;
+ }
+ if (state == State.OnClosingQuote)
+ state = State.InWhiteSpace;
+ continue;
+ }
+ if (field[c] == '\\')
+ {
+ if (state == State.InQuotedToken)
+ {
+ state = State.OnBackslash;
+ continue;
+ }
+ }
+ if (state == State.OnBackslash)
+ {
+ state = State.InQuotedToken;
+ continue;
+ }
+ if (field[c] == '"' || field[c] == '\'' || field[c] == 0x201d)
+ {
+ if (state == State.InWhiteSpace)
+ {
+ quoteStart = field[c];
+ state = State.OnOpeningQuote;
+ continue;
+ }
+ if (state == State.InQuotedToken)
+ {
+ if (field[c] == quoteStart)
+ {
+ tokens.Add(field.Substring(tokenStart, c - tokenStart));
+ state = State.OnClosingQuote;
+ continue;
+ }
+ continue;
+ }
+ if (state == State.OnOpeningQuote)
+ {
+ if (field[c] == quoteStart)
+ {
+ state = State.OnClosingQuote;
+ continue;
+ }
+ else
+ {
+ tokenStart = c;
+ state = State.InQuotedToken;
+ continue;
+ }
+ }
+ continue;
+ }
+ if (state == State.InWhiteSpace)
+ {
+ tokenStart = c;
+ state = State.InToken;
+ continue;
+ }
+ if (state == State.OnOpeningQuote)
+ {
+ tokenStart = c;
+ state = State.InQuotedToken;
+ continue;
+ }
+ if (state == State.OnClosingQuote)
+ {
+ tokenStart = c;
+ state = State.InToken;
+ continue;
+ }
+ }
+ if (state == State.InToken)
+ tokens.Add(field.Substring(tokenStart, field.Length - tokenStart));
+ return tokens.ToArray();
+ }
+
+ public static FieldInfo ParseField(string field)
+ {
+ FieldInfo emptyField = new FieldInfo
+ {
+ FieldType = "",
+ Arguments = new string[] { },
+ Switches = new string[] { },
+ };
+
+ if (field.Length == 0)
+ return emptyField;
+ string fieldType = field.TrimStart().Split(' ').FirstOrDefault();
+ if (fieldType == null || fieldType.ToUpper() != "HYPERLINK" || fieldType.ToUpper() != "REF")
+ return emptyField;
+ string[] tokens = GetTokens(field);
+ if (tokens.Length == 0)
+ return emptyField;
+ FieldInfo fieldInfo = new FieldInfo()
+ {
+ FieldType = tokens[0],
+ Switches = tokens.Where(t => t[0] == '\\').ToArray(),
+ Arguments = tokens.Skip(1).Where(t => t[0] != '\\').ToArray(),
+ };
+ return fieldInfo;
+ }
+ }
+
+ class ContentPartRelTypeIdTuple
+ {
+ public OpenXmlPart ContentPart { get; set; }
+ public string RelationshipType { get; set; }
+ public string RelationshipId { get; set; }
+ }
+
+ // This class is used to prevent duplication of images
+ class ImageData
+ {
+ private string ContentType { get; set; }
+ private byte[] Image { get; set; }
+ public OpenXmlPart ImagePart { get; set; }
+ public List<ContentPartRelTypeIdTuple> ContentPartRelTypeIdList = new List<ContentPartRelTypeIdTuple>();
+
+ public ImageData(ImagePart part)
+ {
+ ContentType = part.ContentType;
+ using (Stream s = part.GetStream(FileMode.Open, FileAccess.Read))
+ {
+ Image = new byte[s.Length];
+ s.Read(Image, 0, (int)s.Length);
+ }
+ }
+
+ public void AddContentPartRelTypeResourceIdTupple(OpenXmlPart contentPart, string relationshipType, string relationshipId)
+ {
+ ContentPartRelTypeIdList.Add(
+ new ContentPartRelTypeIdTuple()
+ {
+ ContentPart = contentPart,
+ RelationshipType = relationshipType,
+ RelationshipId = relationshipId,
+ });
+ }
+
+ public void WriteImage(ImagePart part)
+ {
+ using (Stream s = part.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ s.Write(Image, 0, Image.GetUpperBound(0) + 1);
+ }
+
+ public bool Compare(ImageData arg)
+ {
+ if (ContentType != arg.ContentType)
+ return false;
+ if (Image.GetLongLength(0) != arg.Image.GetLongLength(0))
+ return false;
+ // Compare the arrays byte by byte
+ long length = Image.GetLongLength(0);
+ byte[] image1 = Image;
+ byte[] image2 = arg.Image;
+ for (long n = 0; n < length; n++)
+ if (image1[n] != image2[n])
+ return false;
+ return true;
+ }
+ }
+
+ // This class is used to prevent duplication of media
+ class MediaData
+ {
+ private string ContentType { get; set; }
+ private byte[] Media { get; set; }
+ public DataPart DataPart { get; set; }
+ public List<ContentPartRelTypeIdTuple> ContentPartRelTypeIdList = new List<ContentPartRelTypeIdTuple>();
+
+ public MediaData(DataPart part)
+ {
+ ContentType = part.ContentType;
+ using (Stream s = part.GetStream(FileMode.Open, FileAccess.Read))
+ {
+ Media = new byte[s.Length];
+ s.Read(Media, 0, (int)s.Length);
+ }
+ }
+
+ public void AddContentPartRelTypeResourceIdTupple(OpenXmlPart contentPart, string relationshipType, string relationshipId)
+ {
+ ContentPartRelTypeIdList.Add(
+ new ContentPartRelTypeIdTuple()
+ {
+ ContentPart = contentPart,
+ RelationshipType = relationshipType,
+ RelationshipId = relationshipId,
+ });
+ }
+
+ public void WriteMedia(DataPart part)
+ {
+ using (Stream s = part.GetStream(FileMode.Create, FileAccess.ReadWrite))
+ s.Write(Media, 0, Media.GetUpperBound(0) + 1);
+ }
+
+ public bool Compare(MediaData arg)
+ {
+ if (ContentType != arg.ContentType)
+ return false;
+ if (Media.GetLongLength(0) != arg.Media.GetLongLength(0))
+ return false;
+ // Compare the arrays byte by byte
+ long length = Media.GetLongLength(0);
+ byte[] media1 = Media;
+ byte[] media2 = arg.Media;
+ for (long n = 0; n < length; n++)
+ if (media1[n] != media2[n])
+ return false;
+ return true;
+ }
+ }
+
+#if !NET35
+ public static class UriFixer
+ {
+ public static void FixInvalidUri(Stream fs, Func<string, Uri> invalidUriHandler)
+ {
+ XNamespace relNs = "http://schemas.openxmlformats.org/package/2006/relationships";
+ using (ZipArchive za = new ZipArchive(fs, ZipArchiveMode.Update))
+ {
+ foreach (var entry in za.Entries.ToList())
+ {
+ if (!entry.Name.EndsWith(".rels"))
+ continue;
+ bool replaceEntry = false;
+ XDocument entryXDoc = null;
+ using (var entryStream = entry.Open())
+ {
+ try
+ {
+ entryXDoc = XDocument.Load(entryStream);
+ if (entryXDoc.Root != null && entryXDoc.Root.Name.Namespace == relNs)
+ {
+ var urisToCheck = entryXDoc
+ .Descendants(relNs + "Relationship")
+ .Where(r => r.Attribute("TargetMode") != null && (string)r.Attribute("TargetMode") == "External");
+ foreach (var rel in urisToCheck)
+ {
+ var target = (string)rel.Attribute("Target");
+ if (target != null)
+ {
+ try
+ {
+ Uri uri = new Uri(target);
+ }
+ catch (UriFormatException)
+ {
+ Uri newUri = invalidUriHandler(target);
+ rel.Attribute("Target").Value = newUri.ToString();
+ replaceEntry = true;
+ }
+ }
+ }
+ }
+ }
+ catch (XmlException)
+ {
+ continue;
+ }
+ }
+ if (replaceEntry)
+ {
+ var fullName = entry.FullName;
+ entry.Delete();
+ var newEntry = za.CreateEntry(fullName);
+ using (StreamWriter writer = new StreamWriter(newEntry.Open()))
+ using (XmlWriter xmlWriter = XmlWriter.Create(writer))
+ {
+ entryXDoc.WriteTo(xmlWriter);
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+
+ public static class ACTIVEX
+ {
+ public static readonly XNamespace activex =
+ "http://schemas.microsoft.com/office/2006/activeX";
+ public static readonly XName classid = activex + "classid";
+ public static readonly XName font = activex + "font";
+ public static readonly XName license = activex + "license";
+ public static readonly XName name = activex + "name";
+ public static readonly XName ocx = activex + "ocx";
+ public static readonly XName ocxPr = activex + "ocxPr";
+ public static readonly XName persistence = activex + "persistence";
+ public static readonly XName value = activex + "value";
+ }
+
+ public static class BIBLIO
+ {
+ public static readonly XNamespace biblio =
+ "http://schemas.microsoft.com/office/word/2004/10/bibliography";
+ public static readonly XName AlbumTitle = biblio + "AlbumTitle";
+ public static readonly XName Artist = biblio + "Artist";
+ public static readonly XName Author = biblio + "Author";
+ public static readonly XName City = biblio + "City";
+ public static readonly XName Comments = biblio + "Comments";
+ public static readonly XName Composer = biblio + "Composer";
+ public static readonly XName Conductor = biblio + "Conductor";
+ public static readonly XName ConferenceName = biblio + "ConferenceName";
+ public static readonly XName Country = biblio + "Country";
+ public static readonly XName Day = biblio + "Day";
+ public static readonly XName DayAccessed = biblio + "DayAccessed";
+ public static readonly XName Editor = biblio + "Editor";
+ public static readonly XName First = biblio + "First";
+ public static readonly XName Guid = biblio + "Guid";
+ public static readonly XName InternetSiteTitle = biblio + "InternetSiteTitle";
+ public static readonly XName Inventor = biblio + "Inventor";
+ public static readonly XName Last = biblio + "Last";
+ public static readonly XName LCID = biblio + "LCID";
+ public static readonly XName Main = biblio + "Main";
+ public static readonly XName Medium = biblio + "Medium";
+ public static readonly XName Middle = biblio + "Middle";
+ public static readonly XName Month = biblio + "Month";
+ public static readonly XName MonthAccessed = biblio + "MonthAccessed";
+ public static readonly XName NameList = biblio + "NameList";
+ public static readonly XName NumberVolumes = biblio + "NumberVolumes";
+ public static readonly XName Pages = biblio + "Pages";
+ public static readonly XName PatentNumber = biblio + "PatentNumber";
+ public static readonly XName Performer = biblio + "Performer";
+ public static readonly XName Person = biblio + "Person";
+ public static readonly XName ProducerName = biblio + "ProducerName";
+ public static readonly XName ProductionCompany = biblio + "ProductionCompany";
+ public static readonly XName Publisher = biblio + "Publisher";
+ public static readonly XName RefOrder = biblio + "RefOrder";
+ public static readonly XName ShortTitle = biblio + "ShortTitle";
+ public static readonly XName Source = biblio + "Source";
+ public static readonly XName Sources = biblio + "Sources";
+ public static readonly XName SourceType = biblio + "SourceType";
+ public static readonly XName Tag = biblio + "Tag";
+ public static readonly XName Title = biblio + "Title";
+ public static readonly XName Translator = biblio + "Translator";
+ public static readonly XName Type = biblio + "Type";
+ public static readonly XName URL = biblio + "URL";
+ public static readonly XName Version = biblio + "Version";
+ public static readonly XName Volume = biblio + "Volume";
+ public static readonly XName Year = biblio + "Year";
+ public static readonly XName YearAccessed = biblio + "YearAccessed";
+ }
+
+ public static class INK
+ {
+ public static readonly XNamespace ink =
+ "http://schemas.microsoft.com/ink/2010/main";
+ public static readonly XName context = ink + "context";
+ public static readonly XName sourceLink = ink + "sourceLink";
+ }
+
+ public static class SSNoNamespace
+ {
+ public static readonly XName _ref = "ref";
+ public static readonly XName applyAlignment = "applyAlignment";
+ public static readonly XName applyBorder = "applyBorder";
+ public static readonly XName applyFont = "applyFont";
+ public static readonly XName applyNumberFormat = "applyNumberFormat";
+ public static readonly XName appName = "appName";
+ public static readonly XName baseType = "baseType";
+ public static readonly XName borderId = "borderId";
+ public static readonly XName bottom = "bottom";
+ public static readonly XName builtinId = "builtinId";
+ public static readonly XName calcId = "calcId";
+ public static readonly XName count = "count";
+ public static readonly XName customHeight = "customHeight";
+ public static readonly XName defaultColWidth = "defaultColWidth";
+ public static readonly XName defaultPivotStyle = "defaultPivotStyle";
+ public static readonly XName defaultRowHeight = "defaultRowHeight";
+ public static readonly XName defaultTableStyle = "defaultTableStyle";
+ public static readonly XName defaultThemeVersion = "defaultThemeVersion";
+ public static readonly XName displayName = "displayName";
+ public static readonly XName fillId = "fillId";
+ public static readonly XName fontId = "fontId";
+ public static readonly XName footer = "footer";
+ public static readonly XName formatCode = "formatCode";
+ public static readonly XName header = "header";
+ public static readonly XName horizontal = "horizontal";
+ public static readonly XName ht = "ht";
+ public static readonly XName id = "id";
+ public static readonly XName lastEdited = "lastEdited";
+ public static readonly XName left = "left";
+ public static readonly XName lowestEdited = "lowestEdited";
+ public static readonly XName max = "max";
+ public static readonly XName min = "min";
+ public static readonly XName name = "name";
+ public static readonly XName numFmtId = "numFmtId";
+ public static readonly XName patternType = "patternType";
+ public static readonly XName r = "r";
+ public static readonly XName rgb = "rgb";
+ public static readonly XName right = "right";
+ public static readonly XName rupBuild = "rupBuild";
+ public static readonly XName s = "s";
+ public static readonly XName sheetId = "sheetId";
+ public static readonly XName showColumnStripes = "showColumnStripes";
+ public static readonly XName showFirstColumn = "showFirstColumn";
+ public static readonly XName showLastColumn = "showLastColumn";
+ public static readonly XName showRowStripes = "showRowStripes";
+ public static readonly XName size = "size";
+ public static readonly XName spans = "spans";
+ public static readonly XName sqref = "sqref";
+ public static readonly XName style = "style";
+ public static readonly XName t = "t";
+ public static readonly XName tabSelected = "tabSelected";
+ public static readonly XName theme = "theme";
+ public static readonly XName thickBot = "thickBot";
+ public static readonly XName top = "top";
+ public static readonly XName totalsRowShown = "totalsRowShown";
+ public static readonly XName uniqueCount = "uniqueCount";
+ public static readonly XName val = "val";
+ public static readonly XName width = "width";
+ public static readonly XName windowHeight = "windowHeight";
+ public static readonly XName windowWidth = "windowWidth";
+ public static readonly XName workbookViewId = "workbookViewId";
+ public static readonly XName xfId = "xfId";
+ public static readonly XName xWindow = "xWindow";
+ public static readonly XName yWindow = "yWindow";
+ }
+
+ public static class WNE
+ {
+ public static readonly XNamespace wne =
+ "http://schemas.microsoft.com/office/word/2006/wordml";
+ public static readonly XName acd = wne + "acd";
+ public static readonly XName acdEntry = wne + "acdEntry";
+ public static readonly XName acdManifest = wne + "acdManifest";
+ public static readonly XName acdName = wne + "acdName";
+ public static readonly XName acds = wne + "acds";
+ public static readonly XName active = wne + "active";
+ public static readonly XName argValue = wne + "argValue";
+ public static readonly XName fci = wne + "fci";
+ public static readonly XName fciBasedOn = wne + "fciBasedOn";
+ public static readonly XName fciIndexBasedOn = wne + "fciIndexBasedOn";
+ public static readonly XName fciName = wne + "fciName";
+ public static readonly XName hash = wne + "hash";
+ public static readonly XName kcmPrimary = wne + "kcmPrimary";
+ public static readonly XName kcmSecondary = wne + "kcmSecondary";
+ public static readonly XName keymap = wne + "keymap";
+ public static readonly XName keymaps = wne + "keymaps";
+ public static readonly XName macro = wne + "macro";
+ public static readonly XName macroName = wne + "macroName";
+ public static readonly XName mask = wne + "mask";
+ public static readonly XName recipientData = wne + "recipientData";
+ public static readonly XName recipients = wne + "recipients";
+ public static readonly XName swArg = wne + "swArg";
+ public static readonly XName tcg = wne + "tcg";
+ public static readonly XName toolbarData = wne + "toolbarData";
+ public static readonly XName toolbars = wne + "toolbars";
+ public static readonly XName val = wne + "val";
+ public static readonly XName wch = wne + "wch";
+ }
+
+ public static class WPC
+ {
+ public static readonly XNamespace wpc =
+ "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas";
+ }
+
+ public static class WPG
+ {
+ public static readonly XNamespace wpg =
+ "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup";
+ }
+
+ public static class WPI
+ {
+ public static readonly XNamespace wpi =
+ "http://schemas.microsoft.com/office/word/2010/wordprocessingInk";
+ }
+
+ public static class A
+ {
+ public static readonly XNamespace a =
+ "http://schemas.openxmlformats.org/drawingml/2006/main";
+ public static readonly XName accent1 = a + "accent1";
+ public static readonly XName accent2 = a + "accent2";
+ public static readonly XName accent3 = a + "accent3";
+ public static readonly XName accent4 = a + "accent4";
+ public static readonly XName accent5 = a + "accent5";
+ public static readonly XName accent6 = a + "accent6";
+ public static readonly XName ahLst = a + "ahLst";
+ public static readonly XName ahPolar = a + "ahPolar";
+ public static readonly XName ahXY = a + "ahXY";
+ public static readonly XName alpha = a + "alpha";
+ public static readonly XName alphaBiLevel = a + "alphaBiLevel";
+ public static readonly XName alphaCeiling = a + "alphaCeiling";
+ public static readonly XName alphaFloor = a + "alphaFloor";
+ public static readonly XName alphaInv = a + "alphaInv";
+ public static readonly XName alphaMod = a + "alphaMod";
+ public static readonly XName alphaModFix = a + "alphaModFix";
+ public static readonly XName alphaOff = a + "alphaOff";
+ public static readonly XName alphaOutset = a + "alphaOutset";
+ public static readonly XName alphaRepl = a + "alphaRepl";
+ public static readonly XName anchor = a + "anchor";
+ public static readonly XName arcTo = a + "arcTo";
+ public static readonly XName audioCd = a + "audioCd";
+ public static readonly XName audioFile = a + "audioFile";
+ public static readonly XName avLst = a + "avLst";
+ public static readonly XName backdrop = a + "backdrop";
+ public static readonly XName band1H = a + "band1H";
+ public static readonly XName band1V = a + "band1V";
+ public static readonly XName band2H = a + "band2H";
+ public static readonly XName band2V = a + "band2V";
+ public static readonly XName bevel = a + "bevel";
+ public static readonly XName bevelB = a + "bevelB";
+ public static readonly XName bevelT = a + "bevelT";
+ public static readonly XName bgClr = a + "bgClr";
+ public static readonly XName bgFillStyleLst = a + "bgFillStyleLst";
+ public static readonly XName biLevel = a + "biLevel";
+ public static readonly XName bldChart = a + "bldChart";
+ public static readonly XName bldDgm = a + "bldDgm";
+ public static readonly XName blend = a + "blend";
+ public static readonly XName blip = a + "blip";
+ public static readonly XName blipFill = a + "blipFill";
+ public static readonly XName blue = a + "blue";
+ public static readonly XName blueMod = a + "blueMod";
+ public static readonly XName blueOff = a + "blueOff";
+ public static readonly XName blur = a + "blur";
+ public static readonly XName bodyPr = a + "bodyPr";
+ public static readonly XName bottom = a + "bottom";
+ public static readonly XName br = a + "br";
+ public static readonly XName buAutoNum = a + "buAutoNum";
+ public static readonly XName buBlip = a + "buBlip";
+ public static readonly XName buChar = a + "buChar";
+ public static readonly XName buClr = a + "buClr";
+ public static readonly XName buClrTx = a + "buClrTx";
+ public static readonly XName buFont = a + "buFont";
+ public static readonly XName buFontTx = a + "buFontTx";
+ public static readonly XName buNone = a + "buNone";
+ public static readonly XName buSzPct = a + "buSzPct";
+ public static readonly XName buSzPts = a + "buSzPts";
+ public static readonly XName buSzTx = a + "buSzTx";
+ public static readonly XName camera = a + "camera";
+ public static readonly XName cell3D = a + "cell3D";
+ public static readonly XName chart = a + "chart";
+ public static readonly XName chExt = a + "chExt";
+ public static readonly XName chOff = a + "chOff";
+ public static readonly XName close = a + "close";
+ public static readonly XName clrChange = a + "clrChange";
+ public static readonly XName clrFrom = a + "clrFrom";
+ public static readonly XName clrMap = a + "clrMap";
+ public static readonly XName clrRepl = a + "clrRepl";
+ public static readonly XName clrScheme = a + "clrScheme";
+ public static readonly XName clrTo = a + "clrTo";
+ public static readonly XName cNvCxnSpPr = a + "cNvCxnSpPr";
+ public static readonly XName cNvGraphicFramePr = a + "cNvGraphicFramePr";
+ public static readonly XName cNvGrpSpPr = a + "cNvGrpSpPr";
+ public static readonly XName cNvPicPr = a + "cNvPicPr";
+ public static readonly XName cNvPr = a + "cNvPr";
+ public static readonly XName cNvSpPr = a + "cNvSpPr";
+ public static readonly XName comp = a + "comp";
+ public static readonly XName cont = a + "cont";
+ public static readonly XName contourClr = a + "contourClr";
+ public static readonly XName cs = a + "cs";
+ public static readonly XName cubicBezTo = a + "cubicBezTo";
+ public static readonly XName custClr = a + "custClr";
+ public static readonly XName custClrLst = a + "custClrLst";
+ public static readonly XName custDash = a + "custDash";
+ public static readonly XName custGeom = a + "custGeom";
+ public static readonly XName cxn = a + "cxn";
+ public static readonly XName cxnLst = a + "cxnLst";
+ public static readonly XName cxnSp = a + "cxnSp";
+ public static readonly XName cxnSpLocks = a + "cxnSpLocks";
+ public static readonly XName defPPr = a + "defPPr";
+ public static readonly XName defRPr = a + "defRPr";
+ public static readonly XName dgm = a + "dgm";
+ public static readonly XName dk1 = a + "dk1";
+ public static readonly XName dk2 = a + "dk2";
+ public static readonly XName ds = a + "ds";
+ public static readonly XName duotone = a + "duotone";
+ public static readonly XName ea = a + "ea";
+ public static readonly XName effect = a + "effect";
+ public static readonly XName effectDag = a + "effectDag";
+ public static readonly XName effectLst = a + "effectLst";
+ public static readonly XName effectRef = a + "effectRef";
+ public static readonly XName effectStyle = a + "effectStyle";
+ public static readonly XName effectStyleLst = a + "effectStyleLst";
+ public static readonly XName end = a + "end";
+ public static readonly XName endCxn = a + "endCxn";
+ public static readonly XName endParaRPr = a + "endParaRPr";
+ public static readonly XName ext = a + "ext";
+ public static readonly XName extLst = a + "extLst";
+ public static readonly XName extraClrScheme = a + "extraClrScheme";
+ public static readonly XName extraClrSchemeLst = a + "extraClrSchemeLst";
+ public static readonly XName extrusionClr = a + "extrusionClr";
+ public static readonly XName fgClr = a + "fgClr";
+ public static readonly XName fill = a + "fill";
+ public static readonly XName fillOverlay = a + "fillOverlay";
+ public static readonly XName fillRect = a + "fillRect";
+ public static readonly XName fillRef = a + "fillRef";
+ public static readonly XName fillStyleLst = a + "fillStyleLst";
+ public static readonly XName fillToRect = a + "fillToRect";
+ public static readonly XName firstCol = a + "firstCol";
+ public static readonly XName firstRow = a + "firstRow";
+ public static readonly XName flatTx = a + "flatTx";
+ public static readonly XName fld = a + "fld";
+ public static readonly XName fmtScheme = a + "fmtScheme";
+ public static readonly XName folHlink = a + "folHlink";
+ public static readonly XName font = a + "font";
+ public static readonly XName fontRef = a + "fontRef";
+ public static readonly XName fontScheme = a + "fontScheme";
+ public static readonly XName gamma = a + "gamma";
+ public static readonly XName gd = a + "gd";
+ public static readonly XName gdLst = a + "gdLst";
+ public static readonly XName glow = a + "glow";
+ public static readonly XName gradFill = a + "gradFill";
+ public static readonly XName graphic = a + "graphic";
+ public static readonly XName graphicData = a + "graphicData";
+ public static readonly XName graphicFrame = a + "graphicFrame";
+ public static readonly XName graphicFrameLocks = a + "graphicFrameLocks";
+ public static readonly XName gray = a + "gray";
+ public static readonly XName grayscl = a + "grayscl";
+ public static readonly XName green = a + "green";
+ public static readonly XName greenMod = a + "greenMod";
+ public static readonly XName greenOff = a + "greenOff";
+ public static readonly XName gridCol = a + "gridCol";
+ public static readonly XName grpFill = a + "grpFill";
+ public static readonly XName grpSp = a + "grpSp";
+ public static readonly XName grpSpLocks = a + "grpSpLocks";
+ public static readonly XName grpSpPr = a + "grpSpPr";
+ public static readonly XName gs = a + "gs";
+ public static readonly XName gsLst = a + "gsLst";
+ public static readonly XName headEnd = a + "headEnd";
+ public static readonly XName highlight = a + "highlight";
+ public static readonly XName hlink = a + "hlink";
+ public static readonly XName hlinkClick = a + "hlinkClick";
+ public static readonly XName hlinkHover = a + "hlinkHover";
+ public static readonly XName hlinkMouseOver = a + "hlinkMouseOver";
+ public static readonly XName hsl = a + "hsl";
+ public static readonly XName hslClr = a + "hslClr";
+ public static readonly XName hue = a + "hue";
+ public static readonly XName hueMod = a + "hueMod";
+ public static readonly XName hueOff = a + "hueOff";
+ public static readonly XName innerShdw = a + "innerShdw";
+ public static readonly XName insideH = a + "insideH";
+ public static readonly XName insideV = a + "insideV";
+ public static readonly XName inv = a + "inv";
+ public static readonly XName invGamma = a + "invGamma";
+ public static readonly XName lastCol = a + "lastCol";
+ public static readonly XName lastRow = a + "lastRow";
+ public static readonly XName latin = a + "latin";
+ public static readonly XName left = a + "left";
+ public static readonly XName lightRig = a + "lightRig";
+ public static readonly XName lin = a + "lin";
+ public static readonly XName ln = a + "ln";
+ public static readonly XName lnB = a + "lnB";
+ public static readonly XName lnBlToTr = a + "lnBlToTr";
+ public static readonly XName lnDef = a + "lnDef";
+ public static readonly XName lnL = a + "lnL";
+ public static readonly XName lnR = a + "lnR";
+ public static readonly XName lnRef = a + "lnRef";
+ public static readonly XName lnSpc = a + "lnSpc";
+ public static readonly XName lnStyleLst = a + "lnStyleLst";
+ public static readonly XName lnT = a + "lnT";
+ public static readonly XName lnTlToBr = a + "lnTlToBr";
+ public static readonly XName lnTo = a + "lnTo";
+ public static readonly XName lstStyle = a + "lstStyle";
+ public static readonly XName lt1 = a + "lt1";
+ public static readonly XName lt2 = a + "lt2";
+ public static readonly XName lum = a + "lum";
+ public static readonly XName lumMod = a + "lumMod";
+ public static readonly XName lumOff = a + "lumOff";
+ public static readonly XName lvl1pPr = a + "lvl1pPr";
+ public static readonly XName lvl2pPr = a + "lvl2pPr";
+ public static readonly XName lvl3pPr = a + "lvl3pPr";
+ public static readonly XName lvl4pPr = a + "lvl4pPr";
+ public static readonly XName lvl5pPr = a + "lvl5pPr";
+ public static readonly XName lvl6pPr = a + "lvl6pPr";
+ public static readonly XName lvl7pPr = a + "lvl7pPr";
+ public static readonly XName lvl8pPr = a + "lvl8pPr";
+ public static readonly XName lvl9pPr = a + "lvl9pPr";
+ public static readonly XName majorFont = a + "majorFont";
+ public static readonly XName masterClrMapping = a + "masterClrMapping";
+ public static readonly XName minorFont = a + "minorFont";
+ public static readonly XName miter = a + "miter";
+ public static readonly XName moveTo = a + "moveTo";
+ public static readonly XName neCell = a + "neCell";
+ public static readonly XName noAutofit = a + "noAutofit";
+ public static readonly XName noFill = a + "noFill";
+ public static readonly XName norm = a + "norm";
+ public static readonly XName normAutofit = a + "normAutofit";
+ public static readonly XName nvCxnSpPr = a + "nvCxnSpPr";
+ public static readonly XName nvGraphicFramePr = a + "nvGraphicFramePr";
+ public static readonly XName nvGrpSpPr = a + "nvGrpSpPr";
+ public static readonly XName nvPicPr = a + "nvPicPr";
+ public static readonly XName nvSpPr = a + "nvSpPr";
+ public static readonly XName nwCell = a + "nwCell";
+ public static readonly XName objectDefaults = a + "objectDefaults";
+ public static readonly XName off = a + "off";
+ public static readonly XName outerShdw = a + "outerShdw";
+ public static readonly XName overrideClrMapping = a + "overrideClrMapping";
+ public static readonly XName p = a + "p";
+ public static readonly XName path = a + "path";
+ public static readonly XName pathLst = a + "pathLst";
+ public static readonly XName pattFill = a + "pattFill";
+ public static readonly XName pic = a + "pic";
+ public static readonly XName picLocks = a + "picLocks";
+ public static readonly XName pos = a + "pos";
+ public static readonly XName pPr = a + "pPr";
+ public static readonly XName prstClr = a + "prstClr";
+ public static readonly XName prstDash = a + "prstDash";
+ public static readonly XName prstGeom = a + "prstGeom";
+ public static readonly XName prstShdw = a + "prstShdw";
+ public static readonly XName prstTxWarp = a + "prstTxWarp";
+ public static readonly XName pt = a + "pt";
+ public static readonly XName quadBezTo = a + "quadBezTo";
+ public static readonly XName quickTimeFile = a + "quickTimeFile";
+ public static readonly XName r = a + "r";
+ public static readonly XName rect = a + "rect";
+ public static readonly XName red = a + "red";
+ public static readonly XName redMod = a + "redMod";
+ public static readonly XName redOff = a + "redOff";
+ public static readonly XName reflection = a + "reflection";
+ public static readonly XName relIds = a + "relIds";
+ public static readonly XName relOff = a + "relOff";
+ public static readonly XName right = a + "right";
+ public static readonly XName rot = a + "rot";
+ public static readonly XName round = a + "round";
+ public static readonly XName rPr = a + "rPr";
+ public static readonly XName sat = a + "sat";
+ public static readonly XName satMod = a + "satMod";
+ public static readonly XName satOff = a + "satOff";
+ public static readonly XName scene3d = a + "scene3d";
+ public static readonly XName schemeClr = a + "schemeClr";
+ public static readonly XName scrgbClr = a + "scrgbClr";
+ public static readonly XName seCell = a + "seCell";
+ public static readonly XName shade = a + "shade";
+ public static readonly XName snd = a + "snd";
+ public static readonly XName softEdge = a + "softEdge";
+ public static readonly XName solidFill = a + "solidFill";
+ public static readonly XName sp = a + "sp";
+ public static readonly XName sp3d = a + "sp3d";
+ public static readonly XName spAutoFit = a + "spAutoFit";
+ public static readonly XName spcAft = a + "spcAft";
+ public static readonly XName spcBef = a + "spcBef";
+ public static readonly XName spcPct = a + "spcPct";
+ public static readonly XName spcPts = a + "spcPts";
+ public static readonly XName spDef = a + "spDef";
+ public static readonly XName spLocks = a + "spLocks";
+ public static readonly XName spPr = a + "spPr";
+ public static readonly XName srcRect = a + "srcRect";
+ public static readonly XName srgbClr = a + "srgbClr";
+ public static readonly XName st = a + "st";
+ public static readonly XName stCxn = a + "stCxn";
+ public static readonly XName stretch = a + "stretch";
+ public static readonly XName style = a + "style";
+ public static readonly XName swCell = a + "swCell";
+ public static readonly XName sx = a + "sx";
+ public static readonly XName sy = a + "sy";
+ public static readonly XName sym = a + "sym";
+ public static readonly XName sysClr = a + "sysClr";
+ public static readonly XName t = a + "t";
+ public static readonly XName tab = a + "tab";
+ public static readonly XName tableStyle = a + "tableStyle";
+ public static readonly XName tableStyleId = a + "tableStyleId";
+ public static readonly XName tabLst = a + "tabLst";
+ public static readonly XName tailEnd = a + "tailEnd";
+ public static readonly XName tbl = a + "tbl";
+ public static readonly XName tblBg = a + "tblBg";
+ public static readonly XName tblGrid = a + "tblGrid";
+ public static readonly XName tblPr = a + "tblPr";
+ public static readonly XName tblStyle = a + "tblStyle";
+ public static readonly XName tblStyleLst = a + "tblStyleLst";
+ public static readonly XName tc = a + "tc";
+ public static readonly XName tcBdr = a + "tcBdr";
+ public static readonly XName tcPr = a + "tcPr";
+ public static readonly XName tcStyle = a + "tcStyle";
+ public static readonly XName tcTxStyle = a + "tcTxStyle";
+ public static readonly XName theme = a + "theme";
+ public static readonly XName themeElements = a + "themeElements";
+ public static readonly XName themeOverride = a + "themeOverride";
+ public static readonly XName tile = a + "tile";
+ public static readonly XName tileRect = a + "tileRect";
+ public static readonly XName tint = a + "tint";
+ public static readonly XName top = a + "top";
+ public static readonly XName tr = a + "tr";
+ public static readonly XName txBody = a + "txBody";
+ public static readonly XName txDef = a + "txDef";
+ public static readonly XName txSp = a + "txSp";
+ public static readonly XName uFill = a + "uFill";
+ public static readonly XName uFillTx = a + "uFillTx";
+ public static readonly XName uLn = a + "uLn";
+ public static readonly XName uLnTx = a + "uLnTx";
+ public static readonly XName up = a + "up";
+ public static readonly XName useSpRect = a + "useSpRect";
+ public static readonly XName videoFile = a + "videoFile";
+ public static readonly XName wavAudioFile = a + "wavAudioFile";
+ public static readonly XName wholeTbl = a + "wholeTbl";
+ public static readonly XName xfrm = a + "xfrm";
+ }
+
+ public static class A14
+ {
+ public static readonly XNamespace a14 =
+ "http://schemas.microsoft.com/office/drawing/2010/main";
+ public static readonly XName artisticChalkSketch = a14 + "artisticChalkSketch";
+ public static readonly XName artisticGlass = a14 + "artisticGlass";
+ public static readonly XName artisticGlowEdges = a14 + "artisticGlowEdges";
+ public static readonly XName artisticLightScreen = a14 + "artisticLightScreen";
+ public static readonly XName artisticPlasticWrap = a14 + "artisticPlasticWrap";
+ public static readonly XName artisticTexturizer = a14 + "artisticTexturizer";
+ public static readonly XName backgroundMark = a14 + "backgroundMark";
+ public static readonly XName backgroundRemoval = a14 + "backgroundRemoval";
+ public static readonly XName brightnessContrast = a14 + "brightnessContrast";
+ public static readonly XName cameraTool = a14 + "cameraTool";
+ public static readonly XName colorTemperature = a14 + "colorTemperature";
+ public static readonly XName compatExt = a14 + "compatExt";
+ public static readonly XName cpLocks = a14 + "cpLocks";
+ public static readonly XName extLst = a14 + "extLst";
+ public static readonly XName foregroundMark = a14 + "foregroundMark";
+ public static readonly XName hiddenEffects = a14 + "hiddenEffects";
+ public static readonly XName hiddenFill = a14 + "hiddenFill";
+ public static readonly XName hiddenLine = a14 + "hiddenLine";
+ public static readonly XName hiddenScene3d = a14 + "hiddenScene3d";
+ public static readonly XName hiddenSp3d = a14 + "hiddenSp3d";
+ public static readonly XName imgEffect = a14 + "imgEffect";
+ public static readonly XName imgLayer = a14 + "imgLayer";
+ public static readonly XName imgProps = a14 + "imgProps";
+ public static readonly XName legacySpreadsheetColorIndex = a14 + "legacySpreadsheetColorIndex";
+ public static readonly XName m = a14 + "m";
+ public static readonly XName saturation = a14 + "saturation";
+ public static readonly XName shadowObscured = a14 + "shadowObscured";
+ public static readonly XName sharpenSoften = a14 + "sharpenSoften";
+ public static readonly XName useLocalDpi = a14 + "useLocalDpi";
+ }
+
+ public static class C
+ {
+ public static readonly XNamespace c =
+ "http://schemas.openxmlformats.org/drawingml/2006/chart";
+ public static readonly XName applyToEnd = c + "applyToEnd";
+ public static readonly XName applyToFront = c + "applyToFront";
+ public static readonly XName applyToSides = c + "applyToSides";
+ public static readonly XName area3DChart = c + "area3DChart";
+ public static readonly XName areaChart = c + "areaChart";
+ public static readonly XName auto = c + "auto";
+ public static readonly XName autoTitleDeleted = c + "autoTitleDeleted";
+ public static readonly XName autoUpdate = c + "autoUpdate";
+ public static readonly XName axId = c + "axId";
+ public static readonly XName axPos = c + "axPos";
+ public static readonly XName backWall = c + "backWall";
+ public static readonly XName backward = c + "backward";
+ public static readonly XName bandFmt = c + "bandFmt";
+ public static readonly XName bandFmts = c + "bandFmts";
+ public static readonly XName bar3DChart = c + "bar3DChart";
+ public static readonly XName barChart = c + "barChart";
+ public static readonly XName barDir = c + "barDir";
+ public static readonly XName baseTimeUnit = c + "baseTimeUnit";
+ public static readonly XName bubble3D = c + "bubble3D";
+ public static readonly XName bubbleChart = c + "bubbleChart";
+ public static readonly XName bubbleScale = c + "bubbleScale";
+ public static readonly XName bubbleSize = c + "bubbleSize";
+ public static readonly XName builtInUnit = c + "builtInUnit";
+ public static readonly XName cat = c + "cat";
+ public static readonly XName catAx = c + "catAx";
+ public static readonly XName chart = c + "chart";
+ public static readonly XName chartObject = c + "chartObject";
+ public static readonly XName chartSpace = c + "chartSpace";
+ public static readonly XName clrMapOvr = c + "clrMapOvr";
+ public static readonly XName crossAx = c + "crossAx";
+ public static readonly XName crossBetween = c + "crossBetween";
+ public static readonly XName crosses = c + "crosses";
+ public static readonly XName crossesAt = c + "crossesAt";
+ public static readonly XName custSplit = c + "custSplit";
+ public static readonly XName custUnit = c + "custUnit";
+ public static readonly XName data = c + "data";
+ public static readonly XName date1904 = c + "date1904";
+ public static readonly XName dateAx = c + "dateAx";
+ public static readonly XName delete = c + "delete";
+ public static readonly XName depthPercent = c + "depthPercent";
+ public static readonly XName dispBlanksAs = c + "dispBlanksAs";
+ public static readonly XName dispEq = c + "dispEq";
+ public static readonly XName dispRSqr = c + "dispRSqr";
+ public static readonly XName dispUnits = c + "dispUnits";
+ public static readonly XName dispUnitsLbl = c + "dispUnitsLbl";
+ public static readonly XName dLbl = c + "dLbl";
+ public static readonly XName dLblPos = c + "dLblPos";
+ public static readonly XName dLbls = c + "dLbls";
+ public static readonly XName doughnutChart = c + "doughnutChart";
+ public static readonly XName downBars = c + "downBars";
+ public static readonly XName dPt = c + "dPt";
+ public static readonly XName dropLines = c + "dropLines";
+ public static readonly XName dTable = c + "dTable";
+ public static readonly XName errBars = c + "errBars";
+ public static readonly XName errBarType = c + "errBarType";
+ public static readonly XName errDir = c + "errDir";
+ public static readonly XName errValType = c + "errValType";
+ public static readonly XName explosion = c + "explosion";
+ public static readonly XName ext = c + "ext";
+ public static readonly XName externalData = c + "externalData";
+ public static readonly XName extLst = c + "extLst";
+ public static readonly XName f = c + "f";
+ public static readonly XName firstSliceAng = c + "firstSliceAng";
+ public static readonly XName floor = c + "floor";
+ public static readonly XName fmtId = c + "fmtId";
+ public static readonly XName formatCode = c + "formatCode";
+ public static readonly XName formatting = c + "formatting";
+ public static readonly XName forward = c + "forward";
+ public static readonly XName gapDepth = c + "gapDepth";
+ public static readonly XName gapWidth = c + "gapWidth";
+ public static readonly XName grouping = c + "grouping";
+ public static readonly XName h = c + "h";
+ public static readonly XName headerFooter = c + "headerFooter";
+ public static readonly XName hiLowLines = c + "hiLowLines";
+ public static readonly XName hMode = c + "hMode";
+ public static readonly XName holeSize = c + "holeSize";
+ public static readonly XName hPercent = c + "hPercent";
+ public static readonly XName idx = c + "idx";
+ public static readonly XName intercept = c + "intercept";
+ public static readonly XName invertIfNegative = c + "invertIfNegative";
+ public static readonly XName lang = c + "lang";
+ public static readonly XName layout = c + "layout";
+ public static readonly XName layoutTarget = c + "layoutTarget";
+ public static readonly XName lblAlgn = c + "lblAlgn";
+ public static readonly XName lblOffset = c + "lblOffset";
+ public static readonly XName leaderLines = c + "leaderLines";
+ public static readonly XName legend = c + "legend";
+ public static readonly XName legendEntry = c + "legendEntry";
+ public static readonly XName legendPos = c + "legendPos";
+ public static readonly XName line3DChart = c + "line3DChart";
+ public static readonly XName lineChart = c + "lineChart";
+ public static readonly XName logBase = c + "logBase";
+ public static readonly XName lvl = c + "lvl";
+ public static readonly XName majorGridlines = c + "majorGridlines";
+ public static readonly XName majorTickMark = c + "majorTickMark";
+ public static readonly XName majorTimeUnit = c + "majorTimeUnit";
+ public static readonly XName majorUnit = c + "majorUnit";
+ public static readonly XName manualLayout = c + "manualLayout";
+ public static readonly XName marker = c + "marker";
+ public static readonly XName max = c + "max";
+ public static readonly XName min = c + "min";
+ public static readonly XName minorGridlines = c + "minorGridlines";
+ public static readonly XName minorTickMark = c + "minorTickMark";
+ public static readonly XName minorTimeUnit = c + "minorTimeUnit";
+ public static readonly XName minorUnit = c + "minorUnit";
+ public static readonly XName minus = c + "minus";
+ public static readonly XName multiLvlStrCache = c + "multiLvlStrCache";
+ public static readonly XName multiLvlStrRef = c + "multiLvlStrRef";
+ public static readonly XName name = c + "name";
+ public static readonly XName noEndCap = c + "noEndCap";
+ public static readonly XName noMultiLvlLbl = c + "noMultiLvlLbl";
+ public static readonly XName numCache = c + "numCache";
+ public static readonly XName numFmt = c + "numFmt";
+ public static readonly XName numLit = c + "numLit";
+ public static readonly XName numRef = c + "numRef";
+ public static readonly XName oddFooter = c + "oddFooter";
+ public static readonly XName oddHeader = c + "oddHeader";
+ public static readonly XName ofPieChart = c + "ofPieChart";
+ public static readonly XName ofPieType = c + "ofPieType";
+ public static readonly XName order = c + "order";
+ public static readonly XName orientation = c + "orientation";
+ public static readonly XName overlap = c + "overlap";
+ public static readonly XName overlay = c + "overlay";
+ public static readonly XName pageMargins = c + "pageMargins";
+ public static readonly XName pageSetup = c + "pageSetup";
+ public static readonly XName period = c + "period";
+ public static readonly XName perspective = c + "perspective";
+ public static readonly XName pictureFormat = c + "pictureFormat";
+ public static readonly XName pictureOptions = c + "pictureOptions";
+ public static readonly XName pictureStackUnit = c + "pictureStackUnit";
+ public static readonly XName pie3DChart = c + "pie3DChart";
+ public static readonly XName pieChart = c + "pieChart";
+ public static readonly XName pivotFmt = c + "pivotFmt";
+ public static readonly XName pivotFmts = c + "pivotFmts";
+ public static readonly XName pivotSource = c + "pivotSource";
+ public static readonly XName plotArea = c + "plotArea";
+ public static readonly XName plotVisOnly = c + "plotVisOnly";
+ public static readonly XName plus = c + "plus";
+ public static readonly XName printSettings = c + "printSettings";
+ public static readonly XName protection = c + "protection";
+ public static readonly XName pt = c + "pt";
+ public static readonly XName ptCount = c + "ptCount";
+ public static readonly XName radarChart = c + "radarChart";
+ public static readonly XName radarStyle = c + "radarStyle";
+ public static readonly XName rAngAx = c + "rAngAx";
+ public static readonly XName rich = c + "rich";
+ public static readonly XName rotX = c + "rotX";
+ public static readonly XName rotY = c + "rotY";
+ public static readonly XName roundedCorners = c + "roundedCorners";
+ public static readonly XName scaling = c + "scaling";
+ public static readonly XName scatterChart = c + "scatterChart";
+ public static readonly XName scatterStyle = c + "scatterStyle";
+ public static readonly XName secondPiePt = c + "secondPiePt";
+ public static readonly XName secondPieSize = c + "secondPieSize";
+ public static readonly XName selection = c + "selection";
+ public static readonly XName separator = c + "separator";
+ public static readonly XName ser = c + "ser";
+ public static readonly XName serAx = c + "serAx";
+ public static readonly XName serLines = c + "serLines";
+ public static readonly XName shape = c + "shape";
+ public static readonly XName showBubbleSize = c + "showBubbleSize";
+ public static readonly XName showCatName = c + "showCatName";
+ public static readonly XName showDLblsOverMax = c + "showDLblsOverMax";
+ public static readonly XName showHorzBorder = c + "showHorzBorder";
+ public static readonly XName showKeys = c + "showKeys";
+ public static readonly XName showLeaderLines = c + "showLeaderLines";
+ public static readonly XName showLegendKey = c + "showLegendKey";
+ public static readonly XName showNegBubbles = c + "showNegBubbles";
+ public static readonly XName showOutline = c + "showOutline";
+ public static readonly XName showPercent = c + "showPercent";
+ public static readonly XName showSerName = c + "showSerName";
+ public static readonly XName showVal = c + "showVal";
+ public static readonly XName showVertBorder = c + "showVertBorder";
+ public static readonly XName sideWall = c + "sideWall";
+ public static readonly XName size = c + "size";
+ public static readonly XName sizeRepresents = c + "sizeRepresents";
+ public static readonly XName smooth = c + "smooth";
+ public static readonly XName splitPos = c + "splitPos";
+ public static readonly XName splitType = c + "splitType";
+ public static readonly XName spPr = c + "spPr";
+ public static readonly XName stockChart = c + "stockChart";
+ public static readonly XName strCache = c + "strCache";
+ public static readonly XName strLit = c + "strLit";
+ public static readonly XName strRef = c + "strRef";
+ public static readonly XName style = c + "style";
+ public static readonly XName surface3DChart = c + "surface3DChart";
+ public static readonly XName surfaceChart = c + "surfaceChart";
+ public static readonly XName symbol = c + "symbol";
+ public static readonly XName thickness = c + "thickness";
+ public static readonly XName tickLblPos = c + "tickLblPos";
+ public static readonly XName tickLblSkip = c + "tickLblSkip";
+ public static readonly XName tickMarkSkip = c + "tickMarkSkip";
+ public static readonly XName title = c + "title";
+ public static readonly XName trendline = c + "trendline";
+ public static readonly XName trendlineLbl = c + "trendlineLbl";
+ public static readonly XName trendlineType = c + "trendlineType";
+ public static readonly XName tx = c + "tx";
+ public static readonly XName txPr = c + "txPr";
+ public static readonly XName upBars = c + "upBars";
+ public static readonly XName upDownBars = c + "upDownBars";
+ public static readonly XName userInterface = c + "userInterface";
+ public static readonly XName userShapes = c + "userShapes";
+ public static readonly XName v = c + "v";
+ public static readonly XName val = c + "val";
+ public static readonly XName valAx = c + "valAx";
+ public static readonly XName varyColors = c + "varyColors";
+ public static readonly XName view3D = c + "view3D";
+ public static readonly XName w = c + "w";
+ public static readonly XName wireframe = c + "wireframe";
+ public static readonly XName wMode = c + "wMode";
+ public static readonly XName x = c + "x";
+ public static readonly XName xMode = c + "xMode";
+ public static readonly XName xVal = c + "xVal";
+ public static readonly XName y = c + "y";
+ public static readonly XName yMode = c + "yMode";
+ public static readonly XName yVal = c + "yVal";
+ }
+
+ public static class CDR
+ {
+ public static readonly XNamespace cdr =
+ "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing";
+ public static readonly XName absSizeAnchor = cdr + "absSizeAnchor";
+ public static readonly XName blipFill = cdr + "blipFill";
+ public static readonly XName cNvCxnSpPr = cdr + "cNvCxnSpPr";
+ public static readonly XName cNvGraphicFramePr = cdr + "cNvGraphicFramePr";
+ public static readonly XName cNvGrpSpPr = cdr + "cNvGrpSpPr";
+ public static readonly XName cNvPicPr = cdr + "cNvPicPr";
+ public static readonly XName cNvPr = cdr + "cNvPr";
+ public static readonly XName cNvSpPr = cdr + "cNvSpPr";
+ public static readonly XName cxnSp = cdr + "cxnSp";
+ public static readonly XName ext = cdr + "ext";
+ public static readonly XName from = cdr + "from";
+ public static readonly XName graphicFrame = cdr + "graphicFrame";
+ public static readonly XName grpSp = cdr + "grpSp";
+ public static readonly XName grpSpPr = cdr + "grpSpPr";
+ public static readonly XName nvCxnSpPr = cdr + "nvCxnSpPr";
+ public static readonly XName nvGraphicFramePr = cdr + "nvGraphicFramePr";
+ public static readonly XName nvGrpSpPr = cdr + "nvGrpSpPr";
+ public static readonly XName nvPicPr = cdr + "nvPicPr";
+ public static readonly XName nvSpPr = cdr + "nvSpPr";
+ public static readonly XName pic = cdr + "pic";
+ public static readonly XName relSizeAnchor = cdr + "relSizeAnchor";
+ public static readonly XName sp = cdr + "sp";
+ public static readonly XName spPr = cdr + "spPr";
+ public static readonly XName style = cdr + "style";
+ public static readonly XName to = cdr + "to";
+ public static readonly XName txBody = cdr + "txBody";
+ public static readonly XName x = cdr + "x";
+ public static readonly XName xfrm = cdr + "xfrm";
+ public static readonly XName y = cdr + "y";
+ }
+
+ public static class COM
+ {
+ public static readonly XNamespace com =
+ "http://schemas.openxmlformats.org/drawingml/2006/compatibility";
+ public static readonly XName legacyDrawing = com + "legacyDrawing";
+ }
+
+ public static class CP
+ {
+ public static readonly XNamespace cp =
+ "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
+ public static readonly XName category = cp + "category";
+ public static readonly XName contentStatus = cp + "contentStatus";
+ public static readonly XName contentType = cp + "contentType";
+ public static readonly XName coreProperties = cp + "coreProperties";
+ public static readonly XName keywords = cp + "keywords";
+ public static readonly XName lastModifiedBy = cp + "lastModifiedBy";
+ public static readonly XName lastPrinted = cp + "lastPrinted";
+ public static readonly XName revision = cp + "revision";
+ }
+
+ public static class CUSTPRO
+ {
+ public static readonly XNamespace custpro =
+ "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
+ public static readonly XName Properties = custpro + "Properties";
+ public static readonly XName property = custpro + "property";
+ }
+
+ public static class DC
+ {
+ public static readonly XNamespace dc =
+ "http://purl.org/dc/elements/1.1/";
+ public static readonly XName creator = dc + "creator";
+ public static readonly XName description = dc + "description";
+ public static readonly XName subject = dc + "subject";
+ public static readonly XName title = dc + "title";
+ }
+
+ public static class DCTERMS
+ {
+ public static readonly XNamespace dcterms =
+ "http://purl.org/dc/terms/";
+ public static readonly XName created = dcterms + "created";
+ public static readonly XName modified = dcterms + "modified";
+ }
+
+ public static class DGM
+ {
+ public static readonly XNamespace dgm =
+ "http://schemas.openxmlformats.org/drawingml/2006/diagram";
+ public static readonly XName adj = dgm + "adj";
+ public static readonly XName adjLst = dgm + "adjLst";
+ public static readonly XName alg = dgm + "alg";
+ public static readonly XName animLvl = dgm + "animLvl";
+ public static readonly XName animOne = dgm + "animOne";
+ public static readonly XName bg = dgm + "bg";
+ public static readonly XName bulletEnabled = dgm + "bulletEnabled";
+ public static readonly XName cat = dgm + "cat";
+ public static readonly XName catLst = dgm + "catLst";
+ public static readonly XName chMax = dgm + "chMax";
+ public static readonly XName choose = dgm + "choose";
+ public static readonly XName chPref = dgm + "chPref";
+ public static readonly XName clrData = dgm + "clrData";
+ public static readonly XName colorsDef = dgm + "colorsDef";
+ public static readonly XName constr = dgm + "constr";
+ public static readonly XName constrLst = dgm + "constrLst";
+ public static readonly XName cxn = dgm + "cxn";
+ public static readonly XName cxnLst = dgm + "cxnLst";
+ public static readonly XName dataModel = dgm + "dataModel";
+ public static readonly XName desc = dgm + "desc";
+ public static readonly XName dir = dgm + "dir";
+ public static readonly XName effectClrLst = dgm + "effectClrLst";
+ public static readonly XName _else = dgm + "else";
+ public static readonly XName extLst = dgm + "extLst";
+ public static readonly XName fillClrLst = dgm + "fillClrLst";
+ public static readonly XName forEach = dgm + "forEach";
+ public static readonly XName hierBranch = dgm + "hierBranch";
+ public static readonly XName _if = dgm + "if";
+ public static readonly XName layoutDef = dgm + "layoutDef";
+ public static readonly XName layoutNode = dgm + "layoutNode";
+ public static readonly XName linClrLst = dgm + "linClrLst";
+ public static readonly XName orgChart = dgm + "orgChart";
+ public static readonly XName param = dgm + "param";
+ public static readonly XName presLayoutVars = dgm + "presLayoutVars";
+ public static readonly XName presOf = dgm + "presOf";
+ public static readonly XName prSet = dgm + "prSet";
+ public static readonly XName pt = dgm + "pt";
+ public static readonly XName ptLst = dgm + "ptLst";
+ public static readonly XName relIds = dgm + "relIds";
+ public static readonly XName resizeHandles = dgm + "resizeHandles";
+ public static readonly XName rule = dgm + "rule";
+ public static readonly XName ruleLst = dgm + "ruleLst";
+ public static readonly XName sampData = dgm + "sampData";
+ public static readonly XName scene3d = dgm + "scene3d";
+ public static readonly XName shape = dgm + "shape";
+ public static readonly XName sp3d = dgm + "sp3d";
+ public static readonly XName spPr = dgm + "spPr";
+ public static readonly XName style = dgm + "style";
+ public static readonly XName styleData = dgm + "styleData";
+ public static readonly XName styleDef = dgm + "styleDef";
+ public static readonly XName styleLbl = dgm + "styleLbl";
+ public static readonly XName t = dgm + "t";
+ public static readonly XName title = dgm + "title";
+ public static readonly XName txEffectClrLst = dgm + "txEffectClrLst";
+ public static readonly XName txFillClrLst = dgm + "txFillClrLst";
+ public static readonly XName txLinClrLst = dgm + "txLinClrLst";
+ public static readonly XName txPr = dgm + "txPr";
+ public static readonly XName varLst = dgm + "varLst";
+ public static readonly XName whole = dgm + "whole";
+ }
+
+ public static class DGM14
+ {
+ public static readonly XNamespace dgm14 =
+ "http://schemas.microsoft.com/office/drawing/2010/diagram";
+ public static readonly XName cNvPr = dgm14 + "cNvPr";
+ public static readonly XName recolorImg = dgm14 + "recolorImg";
+ }
+
+ public static class DIGSIG
+ {
+ public static readonly XNamespace digsig =
+ "http://schemas.microsoft.com/office/2006/digsig";
+ public static readonly XName ApplicationVersion = digsig + "ApplicationVersion";
+ public static readonly XName ColorDepth = digsig + "ColorDepth";
+ public static readonly XName HorizontalResolution = digsig + "HorizontalResolution";
+ public static readonly XName ManifestHashAlgorithm = digsig + "ManifestHashAlgorithm";
+ public static readonly XName Monitors = digsig + "Monitors";
+ public static readonly XName OfficeVersion = digsig + "OfficeVersion";
+ public static readonly XName SetupID = digsig + "SetupID";
+ public static readonly XName SignatureComments = digsig + "SignatureComments";
+ public static readonly XName SignatureImage = digsig + "SignatureImage";
+ public static readonly XName SignatureInfoV1 = digsig + "SignatureInfoV1";
+ public static readonly XName SignatureProviderDetails = digsig + "SignatureProviderDetails";
+ public static readonly XName SignatureProviderId = digsig + "SignatureProviderId";
+ public static readonly XName SignatureProviderUrl = digsig + "SignatureProviderUrl";
+ public static readonly XName SignatureText = digsig + "SignatureText";
+ public static readonly XName SignatureType = digsig + "SignatureType";
+ public static readonly XName VerticalResolution = digsig + "VerticalResolution";
+ public static readonly XName WindowsVersion = digsig + "WindowsVersion";
+ }
+
+ public static class DS
+ {
+ public static readonly XNamespace ds =
+ "http://schemas.openxmlformats.org/officeDocument/2006/customXml";
+ public static readonly XName datastoreItem = ds + "datastoreItem";
+ public static readonly XName itemID = ds + "itemID";
+ public static readonly XName schemaRef = ds + "schemaRef";
+ public static readonly XName schemaRefs = ds + "schemaRefs";
+ public static readonly XName uri = ds + "uri";
+ }
+
+ public static class DSP
+ {
+ public static readonly XNamespace dsp =
+ "http://schemas.microsoft.com/office/drawing/2008/diagram";
+ public static readonly XName dataModelExt = dsp + "dataModelExt";
+ }
+
+ public static class EP
+ {
+ public static readonly XNamespace ep =
+ "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
+ public static readonly XName Application = ep + "Application";
+ public static readonly XName AppVersion = ep + "AppVersion";
+ public static readonly XName Characters = ep + "Characters";
+ public static readonly XName CharactersWithSpaces = ep + "CharactersWithSpaces";
+ public static readonly XName Company = ep + "Company";
+ public static readonly XName DocSecurity = ep + "DocSecurity";
+ public static readonly XName HeadingPairs = ep + "HeadingPairs";
+ public static readonly XName HiddenSlides = ep + "HiddenSlides";
+ public static readonly XName HLinks = ep + "HLinks";
+ public static readonly XName HyperlinkBase = ep + "HyperlinkBase";
+ public static readonly XName HyperlinksChanged = ep + "HyperlinksChanged";
+ public static readonly XName Lines = ep + "Lines";
+ public static readonly XName LinksUpToDate = ep + "LinksUpToDate";
+ public static readonly XName Manager = ep + "Manager";
+ public static readonly XName MMClips = ep + "MMClips";
+ public static readonly XName Notes = ep + "Notes";
+ public static readonly XName Pages = ep + "Pages";
+ public static readonly XName Paragraphs = ep + "Paragraphs";
+ public static readonly XName PresentationFormat = ep + "PresentationFormat";
+ public static readonly XName Properties = ep + "Properties";
+ public static readonly XName ScaleCrop = ep + "ScaleCrop";
+ public static readonly XName SharedDoc = ep + "SharedDoc";
+ public static readonly XName Slides = ep + "Slides";
+ public static readonly XName Template = ep + "Template";
+ public static readonly XName TitlesOfParts = ep + "TitlesOfParts";
+ public static readonly XName TotalTime = ep + "TotalTime";
+ public static readonly XName Words = ep + "Words";
+ }
+
+ public static class LC
+ {
+ public static readonly XNamespace lc =
+ "http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas";
+ public static readonly XName lockedCanvas = lc + "lockedCanvas";
+ }
+
+ public static class M
+ {
+ public static readonly XNamespace m =
+ "http://schemas.openxmlformats.org/officeDocument/2006/math";
+ public static readonly XName acc = m + "acc";
+ public static readonly XName accPr = m + "accPr";
+ public static readonly XName aln = m + "aln";
+ public static readonly XName alnAt = m + "alnAt";
+ public static readonly XName alnScr = m + "alnScr";
+ public static readonly XName argPr = m + "argPr";
+ public static readonly XName argSz = m + "argSz";
+ public static readonly XName bar = m + "bar";
+ public static readonly XName barPr = m + "barPr";
+ public static readonly XName baseJc = m + "baseJc";
+ public static readonly XName begChr = m + "begChr";
+ public static readonly XName borderBox = m + "borderBox";
+ public static readonly XName borderBoxPr = m + "borderBoxPr";
+ public static readonly XName box = m + "box";
+ public static readonly XName boxPr = m + "boxPr";
+ public static readonly XName brk = m + "brk";
+ public static readonly XName brkBin = m + "brkBin";
+ public static readonly XName brkBinSub = m + "brkBinSub";
+ public static readonly XName cGp = m + "cGp";
+ public static readonly XName cGpRule = m + "cGpRule";
+ public static readonly XName chr = m + "chr";
+ public static readonly XName count = m + "count";
+ public static readonly XName cSp = m + "cSp";
+ public static readonly XName ctrlPr = m + "ctrlPr";
+ public static readonly XName d = m + "d";
+ public static readonly XName defJc = m + "defJc";
+ public static readonly XName deg = m + "deg";
+ public static readonly XName degHide = m + "degHide";
+ public static readonly XName den = m + "den";
+ public static readonly XName diff = m + "diff";
+ public static readonly XName dispDef = m + "dispDef";
+ public static readonly XName dPr = m + "dPr";
+ public static readonly XName e = m + "e";
+ public static readonly XName endChr = m + "endChr";
+ public static readonly XName eqArr = m + "eqArr";
+ public static readonly XName eqArrPr = m + "eqArrPr";
+ public static readonly XName f = m + "f";
+ public static readonly XName fName = m + "fName";
+ public static readonly XName fPr = m + "fPr";
+ public static readonly XName func = m + "func";
+ public static readonly XName funcPr = m + "funcPr";
+ public static readonly XName groupChr = m + "groupChr";
+ public static readonly XName groupChrPr = m + "groupChrPr";
+ public static readonly XName grow = m + "grow";
+ public static readonly XName hideBot = m + "hideBot";
+ public static readonly XName hideLeft = m + "hideLeft";
+ public static readonly XName hideRight = m + "hideRight";
+ public static readonly XName hideTop = m + "hideTop";
+ public static readonly XName interSp = m + "interSp";
+ public static readonly XName intLim = m + "intLim";
+ public static readonly XName intraSp = m + "intraSp";
+ public static readonly XName jc = m + "jc";
+ public static readonly XName lim = m + "lim";
+ public static readonly XName limLoc = m + "limLoc";
+ public static readonly XName limLow = m + "limLow";
+ public static readonly XName limLowPr = m + "limLowPr";
+ public static readonly XName limUpp = m + "limUpp";
+ public static readonly XName limUppPr = m + "limUppPr";
+ public static readonly XName lit = m + "lit";
+ public static readonly XName lMargin = m + "lMargin";
+ public static readonly XName _m = m + "m";
+ public static readonly XName mathFont = m + "mathFont";
+ public static readonly XName mathPr = m + "mathPr";
+ public static readonly XName maxDist = m + "maxDist";
+ public static readonly XName mc = m + "mc";
+ public static readonly XName mcJc = m + "mcJc";
+ public static readonly XName mcPr = m + "mcPr";
+ public static readonly XName mcs = m + "mcs";
+ public static readonly XName mPr = m + "mPr";
+ public static readonly XName mr = m + "mr";
+ public static readonly XName nary = m + "nary";
+ public static readonly XName naryLim = m + "naryLim";
+ public static readonly XName naryPr = m + "naryPr";
+ public static readonly XName noBreak = m + "noBreak";
+ public static readonly XName nor = m + "nor";
+ public static readonly XName num = m + "num";
+ public static readonly XName objDist = m + "objDist";
+ public static readonly XName oMath = m + "oMath";
+ public static readonly XName oMathPara = m + "oMathPara";
+ public static readonly XName oMathParaPr = m + "oMathParaPr";
+ public static readonly XName opEmu = m + "opEmu";
+ public static readonly XName phant = m + "phant";
+ public static readonly XName phantPr = m + "phantPr";
+ public static readonly XName plcHide = m + "plcHide";
+ public static readonly XName pos = m + "pos";
+ public static readonly XName postSp = m + "postSp";
+ public static readonly XName preSp = m + "preSp";
+ public static readonly XName r = m + "r";
+ public static readonly XName rad = m + "rad";
+ public static readonly XName radPr = m + "radPr";
+ public static readonly XName rMargin = m + "rMargin";
+ public static readonly XName rPr = m + "rPr";
+ public static readonly XName rSp = m + "rSp";
+ public static readonly XName rSpRule = m + "rSpRule";
+ public static readonly XName scr = m + "scr";
+ public static readonly XName sepChr = m + "sepChr";
+ public static readonly XName show = m + "show";
+ public static readonly XName shp = m + "shp";
+ public static readonly XName smallFrac = m + "smallFrac";
+ public static readonly XName sPre = m + "sPre";
+ public static readonly XName sPrePr = m + "sPrePr";
+ public static readonly XName sSub = m + "sSub";
+ public static readonly XName sSubPr = m + "sSubPr";
+ public static readonly XName sSubSup = m + "sSubSup";
+ public static readonly XName sSubSupPr = m + "sSubSupPr";
+ public static readonly XName sSup = m + "sSup";
+ public static readonly XName sSupPr = m + "sSupPr";
+ public static readonly XName strikeBLTR = m + "strikeBLTR";
+ public static readonly XName strikeH = m + "strikeH";
+ public static readonly XName strikeTLBR = m + "strikeTLBR";
+ public static readonly XName strikeV = m + "strikeV";
+ public static readonly XName sty = m + "sty";
+ public static readonly XName sub = m + "sub";
+ public static readonly XName subHide = m + "subHide";
+ public static readonly XName sup = m + "sup";
+ public static readonly XName supHide = m + "supHide";
+ public static readonly XName t = m + "t";
+ public static readonly XName transp = m + "transp";
+ public static readonly XName type = m + "type";
+ public static readonly XName val = m + "val";
+ public static readonly XName vertJc = m + "vertJc";
+ public static readonly XName wrapIndent = m + "wrapIndent";
+ public static readonly XName wrapRight = m + "wrapRight";
+ public static readonly XName zeroAsc = m + "zeroAsc";
+ public static readonly XName zeroDesc = m + "zeroDesc";
+ public static readonly XName zeroWid = m + "zeroWid";
+ }
+
+ public static class MC
+ {
+ public static readonly XNamespace mc =
+ "http://schemas.openxmlformats.org/markup-compatibility/2006";
+ public static readonly XName AlternateContent = mc + "AlternateContent";
+ public static readonly XName Choice = mc + "Choice";
+ public static readonly XName Fallback = mc + "Fallback";
+ public static readonly XName Ignorable = mc + "Ignorable";
+ public static readonly XName PreserveAttributes = mc + "PreserveAttributes";
+ }
+
+ public static class MDSSI
+ {
+ public static readonly XNamespace mdssi =
+ "http://schemas.openxmlformats.org/package/2006/digital-signature";
+ public static readonly XName Format = mdssi + "Format";
+ public static readonly XName RelationshipReference = mdssi + "RelationshipReference";
+ public static readonly XName SignatureTime = mdssi + "SignatureTime";
+ public static readonly XName Value = mdssi + "Value";
+ }
+
+ public static class MP
+ {
+ public static readonly XNamespace mp =
+ "http://schemas.microsoft.com/office/mac/powerpoint/2008/main";
+ public static readonly XName cube = mp + "cube";
+ public static readonly XName flip = mp + "flip";
+ public static readonly XName transition = mp + "transition";
+ }
+
+ public static class MV
+ {
+ public static readonly XNamespace mv =
+ "urn:schemas-microsoft-com:mac:vml";
+ public static readonly XName blur = mv + "blur";
+ public static readonly XName complextextbox = mv + "complextextbox";
+ }
+
+ public static class NoNamespace
+ {
+ public static readonly XName a = "a";
+ public static readonly XName accent1 = "accent1";
+ public static readonly XName accent2 = "accent2";
+ public static readonly XName accent3 = "accent3";
+ public static readonly XName accent4 = "accent4";
+ public static readonly XName accent5 = "accent5";
+ public static readonly XName accent6 = "accent6";
+ public static readonly XName action = "action";
+ public static readonly XName activeCell = "activeCell";
+ public static readonly XName activeCol = "activeCol";
+ public static readonly XName activePane = "activePane";
+ public static readonly XName activeRow = "activeRow";
+ public static readonly XName advise = "advise";
+ public static readonly XName algn = "algn";
+ public static readonly XName Algorithm = "Algorithm";
+ public static readonly XName alignWithMargins = "alignWithMargins";
+ public static readonly XName allowcomments = "allowcomments";
+ public static readonly XName allowOverlap = "allowOverlap";
+ public static readonly XName allUniqueName = "allUniqueName";
+ public static readonly XName alt = "alt";
+ public static readonly XName alwaysShow = "alwaysShow";
+ public static readonly XName amount = "amount";
+ public static readonly XName amt = "amt";
+ public static readonly XName anchor = "anchor";
+ public static readonly XName anchorCtr = "anchorCtr";
+ public static readonly XName ang = "ang";
+ public static readonly XName animBg = "animBg";
+ public static readonly XName annotation = "annotation";
+ public static readonly XName applyAlignment = "applyAlignment";
+ public static readonly XName applyAlignmentFormats = "applyAlignmentFormats";
+ public static readonly XName applyBorder = "applyBorder";
+ public static readonly XName applyBorderFormats = "applyBorderFormats";
+ public static readonly XName applyFill = "applyFill";
+ public static readonly XName applyFont = "applyFont";
+ public static readonly XName applyFontFormats = "applyFontFormats";
+ public static readonly XName applyNumberFormat = "applyNumberFormat";
+ public static readonly XName applyNumberFormats = "applyNumberFormats";
+ public static readonly XName applyPatternFormats = "applyPatternFormats";
+ public static readonly XName applyProtection = "applyProtection";
+ public static readonly XName applyWidthHeightFormats = "applyWidthHeightFormats";
+ public static readonly XName arcsize = "arcsize";
+ public static readonly XName arg = "arg";
+ public static readonly XName aspectratio = "aspectratio";
+ public static readonly XName assign = "assign";
+ public static readonly XName attribute = "attribute";
+ public static readonly XName author = "author";
+ public static readonly XName authorId = "authorId";
+ public static readonly XName auto = "auto";
+ public static readonly XName autoEnd = "autoEnd";
+ public static readonly XName autoFormatId = "autoFormatId";
+ public static readonly XName autoLine = "autoLine";
+ public static readonly XName autoStart = "autoStart";
+ public static readonly XName axis = "axis";
+ public static readonly XName b = "b";
+ public static readonly XName backdepth = "backdepth";
+ public static readonly XName bandRow = "bandRow";
+ public static readonly XName _base = "base";
+ public static readonly XName baseField = "baseField";
+ public static readonly XName baseItem = "baseItem";
+ public static readonly XName baseline = "baseline";
+ public static readonly XName baseType = "baseType";
+ public static readonly XName behindDoc = "behindDoc";
+ public static readonly XName bestFit = "bestFit";
+ public static readonly XName bg1 = "bg1";
+ public static readonly XName bg2 = "bg2";
+ public static readonly XName bIns = "bIns";
+ public static readonly XName bld = "bld";
+ public static readonly XName bldStep = "bldStep";
+ public static readonly XName blend = "blend";
+ public static readonly XName blurRad = "blurRad";
+ public static readonly XName bmkName = "bmkName";
+ public static readonly XName borderId = "borderId";
+ public static readonly XName bottom = "bottom";
+ public static readonly XName bright = "bright";
+ public static readonly XName brightness = "brightness";
+ public static readonly XName builtinId = "builtinId";
+ public static readonly XName bwMode = "bwMode";
+ public static readonly XName by = "by";
+ public static readonly XName c = "c";
+ public static readonly XName cacheId = "cacheId";
+ public static readonly XName cacheIndex = "cacheIndex";
+ public static readonly XName calcmode = "calcmode";
+ public static readonly XName cap = "cap";
+ public static readonly XName caption = "caption";
+ public static readonly XName categoryIdx = "categoryIdx";
+ public static readonly XName cell = "cell";
+ public static readonly XName cellColor = "cellColor";
+ public static readonly XName cellRange = "cellRange";
+ public static readonly XName _char = "char";
+ public static readonly XName charset = "charset";
+ public static readonly XName chart = "chart";
+ public static readonly XName clearComments = "clearComments";
+ public static readonly XName clearFormats = "clearFormats";
+ public static readonly XName click = "click";
+ public static readonly XName clientInsertedTime = "clientInsertedTime";
+ public static readonly XName clrIdx = "clrIdx";
+ public static readonly XName clrSpc = "clrSpc";
+ public static readonly XName cmd = "cmd";
+ public static readonly XName cmpd = "cmpd";
+ public static readonly XName codeName = "codeName";
+ public static readonly XName coerce = "coerce";
+ public static readonly XName colId = "colId";
+ public static readonly XName color = "color";
+ public static readonly XName colors = "colors";
+ public static readonly XName colorTemp = "colorTemp";
+ public static readonly XName colPageCount = "colPageCount";
+ public static readonly XName cols = "cols";
+ public static readonly XName comma = "comma";
+ public static readonly XName command = "command";
+ public static readonly XName commandType = "commandType";
+ public static readonly XName comment = "comment";
+ public static readonly XName compatLnSpc = "compatLnSpc";
+ public static readonly XName concurrent = "concurrent";
+ public static readonly XName connection = "connection";
+ public static readonly XName connectionId = "connectionId";
+ public static readonly XName connectloc = "connectloc";
+ public static readonly XName consecutive = "consecutive";
+ public static readonly XName constrainbounds = "constrainbounds";
+ public static readonly XName containsInteger = "containsInteger";
+ public static readonly XName containsNumber = "containsNumber";
+ public static readonly XName containsSemiMixedTypes = "containsSemiMixedTypes";
+ public static readonly XName containsString = "containsString";
+ public static readonly XName contrast = "contrast";
+ public static readonly XName control1 = "control1";
+ public static readonly XName control2 = "control2";
+ public static readonly XName coordorigin = "coordorigin";
+ public static readonly XName coordsize = "coordsize";
+ public static readonly XName copy = "copy";
+ public static readonly XName count = "count";
+ public static readonly XName createdVersion = "createdVersion";
+ public static readonly XName cryptAlgorithmClass = "cryptAlgorithmClass";
+ public static readonly XName cryptAlgorithmSid = "cryptAlgorithmSid";
+ public static readonly XName cryptAlgorithmType = "cryptAlgorithmType";
+ public static readonly XName cryptProviderType = "cryptProviderType";
+ public static readonly XName csCatId = "csCatId";
+ public static readonly XName cstate = "cstate";
+ public static readonly XName csTypeId = "csTypeId";
+ public static readonly XName culture = "culture";
+ public static readonly XName current = "current";
+ public static readonly XName customFormat = "customFormat";
+ public static readonly XName customList = "customList";
+ public static readonly XName customWidth = "customWidth";
+ public static readonly XName cx = "cx";
+ public static readonly XName cy = "cy";
+ public static readonly XName d = "d";
+ public static readonly XName data = "data";
+ public static readonly XName dataCaption = "dataCaption";
+ public static readonly XName dataDxfId = "dataDxfId";
+ public static readonly XName dataField = "dataField";
+ public static readonly XName dateTime = "dateTime";
+ public static readonly XName dateTimeGrouping = "dateTimeGrouping";
+ public static readonly XName dde = "dde";
+ public static readonly XName ddeService = "ddeService";
+ public static readonly XName ddeTopic = "ddeTopic";
+ public static readonly XName def = "def";
+ public static readonly XName defaultMemberUniqueName = "defaultMemberUniqueName";
+ public static readonly XName defaultPivotStyle = "defaultPivotStyle";
+ public static readonly XName defaultRowHeight = "defaultRowHeight";
+ public static readonly XName defaultSize = "defaultSize";
+ public static readonly XName defaultTableStyle = "defaultTableStyle";
+ public static readonly XName defStyle = "defStyle";
+ public static readonly XName defTabSz = "defTabSz";
+ public static readonly XName degree = "degree";
+ public static readonly XName delay = "delay";
+ public static readonly XName descending = "descending";
+ public static readonly XName descr = "descr";
+ public static readonly XName destId = "destId";
+ public static readonly XName destination = "destination";
+ public static readonly XName destinationFile = "destinationFile";
+ public static readonly XName destOrd = "destOrd";
+ public static readonly XName dgmfontsize = "dgmfontsize";
+ public static readonly XName dgmstyle = "dgmstyle";
+ public static readonly XName diagonalDown = "diagonalDown";
+ public static readonly XName diagonalUp = "diagonalUp";
+ public static readonly XName dimension = "dimension";
+ public static readonly XName dimensionUniqueName = "dimensionUniqueName";
+ public static readonly XName dir = "dir";
+ public static readonly XName dirty = "dirty";
+ public static readonly XName display = "display";
+ public static readonly XName displayFolder = "displayFolder";
+ public static readonly XName displayName = "displayName";
+ public static readonly XName dist = "dist";
+ public static readonly XName distB = "distB";
+ public static readonly XName distL = "distL";
+ public static readonly XName distR = "distR";
+ public static readonly XName distT = "distT";
+ public static readonly XName divId = "divId";
+ public static readonly XName dpi = "dpi";
+ public static readonly XName dr = "dr";
+ public static readonly XName DrawAspect = "DrawAspect";
+ public static readonly XName dt = "dt";
+ public static readonly XName dur = "dur";
+ public static readonly XName dx = "dx";
+ public static readonly XName dxfId = "dxfId";
+ public static readonly XName dy = "dy";
+ public static readonly XName dz = "dz";
+ public static readonly XName eaLnBrk = "eaLnBrk";
+ public static readonly XName eb = "eb";
+ public static readonly XName edited = "edited";
+ public static readonly XName editPage = "editPage";
+ public static readonly XName end = "end";
+ public static readonly XName endA = "endA";
+ public static readonly XName endangle = "endangle";
+ public static readonly XName endDate = "endDate";
+ public static readonly XName endPos = "endPos";
+ public static readonly XName endSnd = "endSnd";
+ public static readonly XName eqn = "eqn";
+ public static readonly XName evalOrder = "evalOrder";
+ public static readonly XName evt = "evt";
+ public static readonly XName exp = "exp";
+ public static readonly XName extProperty = "extProperty";
+ public static readonly XName f = "f";
+ public static readonly XName fact = "fact";
+ public static readonly XName field = "field";
+ public static readonly XName fieldId = "fieldId";
+ public static readonly XName fieldListSortAscending = "fieldListSortAscending";
+ public static readonly XName fieldPosition = "fieldPosition";
+ public static readonly XName fileType = "fileType";
+ public static readonly XName fillcolor = "fillcolor";
+ public static readonly XName filled = "filled";
+ public static readonly XName fillId = "fillId";
+ public static readonly XName filter = "filter";
+ public static readonly XName filterVal = "filterVal";
+ public static readonly XName first = "first";
+ public static readonly XName firstDataCol = "firstDataCol";
+ public static readonly XName firstDataRow = "firstDataRow";
+ public static readonly XName firstHeaderRow = "firstHeaderRow";
+ public static readonly XName firstRow = "firstRow";
+ public static readonly XName fitshape = "fitshape";
+ public static readonly XName fitToPage = "fitToPage";
+ public static readonly XName fld = "fld";
+ public static readonly XName flip = "flip";
+ public static readonly XName fmla = "fmla";
+ public static readonly XName fmtid = "fmtid";
+ public static readonly XName folHlink = "folHlink";
+ public static readonly XName followColorScheme = "followColorScheme";
+ public static readonly XName fontId = "fontId";
+ public static readonly XName footer = "footer";
+ public static readonly XName _for = "for";
+ public static readonly XName forceAA = "forceAA";
+ public static readonly XName format = "format";
+ public static readonly XName formatCode = "formatCode";
+ public static readonly XName formula = "formula";
+ public static readonly XName forName = "forName";
+ public static readonly XName fov = "fov";
+ public static readonly XName frame = "frame";
+ public static readonly XName from = "from";
+ public static readonly XName fromWordArt = "fromWordArt";
+ public static readonly XName fullCalcOnLoad = "fullCalcOnLoad";
+ public static readonly XName func = "func";
+ public static readonly XName g = "g";
+ public static readonly XName gdRefAng = "gdRefAng";
+ public static readonly XName gdRefR = "gdRefR";
+ public static readonly XName gdRefX = "gdRefX";
+ public static readonly XName gdRefY = "gdRefY";
+ public static readonly XName goal = "goal";
+ public static readonly XName gradientshapeok = "gradientshapeok";
+ public static readonly XName groupBy = "groupBy";
+ public static readonly XName grpId = "grpId";
+ public static readonly XName guid = "guid";
+ public static readonly XName h = "h";
+ public static readonly XName hangingPunct = "hangingPunct";
+ public static readonly XName hashData = "hashData";
+ public static readonly XName header = "header";
+ public static readonly XName headerRowBorderDxfId = "headerRowBorderDxfId";
+ public static readonly XName headerRowDxfId = "headerRowDxfId";
+ public static readonly XName hidden = "hidden";
+ public static readonly XName hier = "hier";
+ public static readonly XName hierarchy = "hierarchy";
+ public static readonly XName hierarchyUsage = "hierarchyUsage";
+ public static readonly XName highlightClick = "highlightClick";
+ public static readonly XName hlink = "hlink";
+ public static readonly XName horizontal = "horizontal";
+ public static readonly XName horizontalCentered = "horizontalCentered";
+ public static readonly XName horizontalDpi = "horizontalDpi";
+ public static readonly XName horzOverflow = "horzOverflow";
+ public static readonly XName href = "href";
+ public static readonly XName hR = "hR";
+ public static readonly XName htmlFormat = "htmlFormat";
+ public static readonly XName htmlTables = "htmlTables";
+ public static readonly XName hue = "hue";
+ public static readonly XName i = "i";
+ public static readonly XName i1 = "i1";
+ public static readonly XName iconId = "iconId";
+ public static readonly XName iconSet = "iconSet";
+ public static readonly XName id = "id";
+ public static readonly XName Id = "Id";
+ public static readonly XName iddest = "iddest";
+ public static readonly XName idref = "idref";
+ public static readonly XName idsrc = "idsrc";
+ public static readonly XName idx = "idx";
+ public static readonly XName imgH = "imgH";
+ public static readonly XName imgW = "imgW";
+ public static readonly XName _in = "in";
+ public static readonly XName includeNewItemsInFilter = "includeNewItemsInFilter";
+ public static readonly XName indent = "indent";
+ public static readonly XName index = "index";
+ public static readonly XName indexed = "indexed";
+ public static readonly XName initials = "initials";
+ public static readonly XName insetpen = "insetpen";
+ public static readonly XName invalEndChars = "invalEndChars";
+ public static readonly XName invalidUrl = "invalidUrl";
+ public static readonly XName invalStChars = "invalStChars";
+ public static readonly XName isInverted = "isInverted";
+ public static readonly XName issignatureline = "issignatureline";
+ public static readonly XName item = "item";
+ public static readonly XName itemPrintTitles = "itemPrintTitles";
+ public static readonly XName joinstyle = "joinstyle";
+ public static readonly XName justifyLastLine = "justifyLastLine";
+ public static readonly XName key = "key";
+ public static readonly XName keyAttribute = "keyAttribute";
+ public static readonly XName l = "l";
+ public static readonly XName lang = "lang";
+ public static readonly XName lastClr = "lastClr";
+ public static readonly XName lastIdx = "lastIdx";
+ public static readonly XName lat = "lat";
+ public static readonly XName latinLnBrk = "latinLnBrk";
+ public static readonly XName layout = "layout";
+ public static readonly XName layoutInCell = "layoutInCell";
+ public static readonly XName left = "left";
+ public static readonly XName len = "len";
+ public static readonly XName length = "length";
+ public static readonly XName level = "level";
+ public static readonly XName lightharsh2 = "lightharsh2";
+ public static readonly XName lightlevel = "lightlevel";
+ public static readonly XName lightlevel2 = "lightlevel2";
+ public static readonly XName lightposition = "lightposition";
+ public static readonly XName lightposition2 = "lightposition2";
+ public static readonly XName lim = "lim";
+ public static readonly XName link = "link";
+ public static readonly XName lIns = "lIns";
+ public static readonly XName loCatId = "loCatId";
+ public static readonly XName locked = "locked";
+ public static readonly XName lon = "lon";
+ public static readonly XName loop = "loop";
+ public static readonly XName loTypeId = "loTypeId";
+ public static readonly XName lum = "lum";
+ public static readonly XName lvl = "lvl";
+ public static readonly XName macro = "macro";
+ public static readonly XName man = "man";
+ public static readonly XName manualBreakCount = "manualBreakCount";
+ public static readonly XName mapId = "mapId";
+ public static readonly XName marL = "marL";
+ public static readonly XName max = "max";
+ public static readonly XName maxAng = "maxAng";
+ public static readonly XName maxR = "maxR";
+ public static readonly XName maxRank = "maxRank";
+ public static readonly XName maxSheetId = "maxSheetId";
+ public static readonly XName maxValue = "maxValue";
+ public static readonly XName maxX = "maxX";
+ public static readonly XName maxY = "maxY";
+ public static readonly XName mdx = "mdx";
+ public static readonly XName measureGroup = "measureGroup";
+ public static readonly XName memberName = "memberName";
+ public static readonly XName merge = "merge";
+ public static readonly XName meth = "meth";
+ public static readonly XName min = "min";
+ public static readonly XName minAng = "minAng";
+ public static readonly XName minR = "minR";
+ public static readonly XName minRefreshableVersion = "minRefreshableVersion";
+ public static readonly XName minSupportedVersion = "minSupportedVersion";
+ public static readonly XName minValue = "minValue";
+ public static readonly XName minVer = "minVer";
+ public static readonly XName minX = "minX";
+ public static readonly XName minY = "minY";
+ public static readonly XName modelId = "modelId";
+ public static readonly XName moveWithCells = "moveWithCells";
+ public static readonly XName n = "n";
+ public static readonly XName name = "name";
+ public static readonly XName _new = "new";
+ public static readonly XName newLength = "newLength";
+ public static readonly XName newName = "newName";
+ public static readonly XName nextAc = "nextAc";
+ public static readonly XName nextId = "nextId";
+ public static readonly XName noChangeArrowheads = "noChangeArrowheads";
+ public static readonly XName noChangeAspect = "noChangeAspect";
+ public static readonly XName noChangeShapeType = "noChangeShapeType";
+ public static readonly XName nodeType = "nodeType";
+ public static readonly XName noEditPoints = "noEditPoints";
+ public static readonly XName noGrp = "noGrp";
+ public static readonly XName noRot = "noRot";
+ public static readonly XName noUngrp = "noUngrp";
+ public static readonly XName np = "np";
+ public static readonly XName ns = "ns";
+ public static readonly XName numCol = "numCol";
+ public static readonly XName numFmtId = "numFmtId";
+ public static readonly XName o = "o";
+ public static readonly XName ObjectID = "ObjectID";
+ public static readonly XName objects = "objects";
+ public static readonly XName ObjectType = "ObjectType";
+ public static readonly XName objId = "objId";
+ public static readonly XName offset = "offset";
+ public static readonly XName old = "old";
+ public static readonly XName oldComment = "oldComment";
+ public static readonly XName oldName = "oldName";
+ public static readonly XName oleUpdate = "oleUpdate";
+ public static readonly XName on = "on";
+ public static readonly XName op = "op";
+ public static readonly XName orient = "orient";
+ public static readonly XName orientation = "orientation";
+ public static readonly XName origin = "origin";
+ public static readonly XName _out = "out";
+ public static readonly XName outline = "outline";
+ public static readonly XName outlineData = "outlineData";
+ public static readonly XName p = "p";
+ public static readonly XName pane = "pane";
+ public static readonly XName panose = "panose";
+ public static readonly XName paperSize = "paperSize";
+ public static readonly XName par = "par";
+ public static readonly XName parameterType = "parameterType";
+ public static readonly XName parent = "parent";
+ public static readonly XName password = "password";
+ public static readonly XName pasteAll = "pasteAll";
+ public static readonly XName pasteValues = "pasteValues";
+ public static readonly XName path = "path";
+ public static readonly XName pathEditMode = "pathEditMode";
+ public static readonly XName patternType = "patternType";
+ public static readonly XName phldr = "phldr";
+ public static readonly XName pid = "pid";
+ public static readonly XName pitchFamily = "pitchFamily";
+ public static readonly XName pivot = "pivot";
+ public static readonly XName points = "points";
+ public static readonly XName pos = "pos";
+ public static readonly XName position = "position";
+ public static readonly XName post = "post";
+ public static readonly XName preferPic = "preferPic";
+ public static readonly XName preserve = "preserve";
+ public static readonly XName pressure = "pressure";
+ public static readonly XName previousCol = "previousCol";
+ public static readonly XName previousRow = "previousRow";
+ public static readonly XName pri = "pri";
+ public static readonly XName priority = "priority";
+ public static readonly XName progId = "progId";
+ public static readonly XName ProgID = "ProgID";
+ public static readonly XName provid = "provid";
+ public static readonly XName prst = "prst";
+ public static readonly XName prstMaterial = "prstMaterial";
+ public static readonly XName ptsTypes = "ptsTypes";
+ public static readonly XName ptType = "ptType";
+ public static readonly XName qsCatId = "qsCatId";
+ public static readonly XName qsTypeId = "qsTypeId";
+ public static readonly XName r = "r";
+ public static readonly XName rad = "rad";
+ public static readonly XName readingOrder = "readingOrder";
+ public static readonly XName recordCount = "recordCount";
+ public static readonly XName _ref = "ref";
+ public static readonly XName ref3D = "ref3D";
+ public static readonly XName refersTo = "refersTo";
+ public static readonly XName refreshedBy = "refreshedBy";
+ public static readonly XName refreshedDate = "refreshedDate";
+ public static readonly XName refreshedVersion = "refreshedVersion";
+ public static readonly XName refreshOnLoad = "refreshOnLoad";
+ public static readonly XName refType = "refType";
+ public static readonly XName relativeFrom = "relativeFrom";
+ public static readonly XName relativeHeight = "relativeHeight";
+ public static readonly XName relId = "relId";
+ public static readonly XName Requires = "Requires";
+ public static readonly XName restart = "restart";
+ public static readonly XName rev = "rev";
+ public static readonly XName rgb = "rgb";
+ public static readonly XName rId = "rId";
+ public static readonly XName rig = "rig";
+ public static readonly XName right = "right";
+ public static readonly XName rIns = "rIns";
+ public static readonly XName rot = "rot";
+ public static readonly XName rotWithShape = "rotWithShape";
+ public static readonly XName rowColShift = "rowColShift";
+ public static readonly XName rowDrillCount = "rowDrillCount";
+ public static readonly XName rowPageCount = "rowPageCount";
+ public static readonly XName rows = "rows";
+ public static readonly XName rtl = "rtl";
+ public static readonly XName rtlCol = "rtlCol";
+ public static readonly XName s = "s";
+ public static readonly XName saltData = "saltData";
+ public static readonly XName sat = "sat";
+ public static readonly XName saveData = "saveData";
+ public static readonly XName saveSubsetFonts = "saveSubsetFonts";
+ public static readonly XName sb = "sb";
+ public static readonly XName scaled = "scaled";
+ public static readonly XName scaling = "scaling";
+ public static readonly XName scenarios = "scenarios";
+ public static readonly XName scope = "scope";
+ public static readonly XName script = "script";
+ public static readonly XName securityDescriptor = "securityDescriptor";
+ public static readonly XName seek = "seek";
+ public static readonly XName sendLocale = "sendLocale";
+ public static readonly XName series = "series";
+ public static readonly XName seriesIdx = "seriesIdx";
+ public static readonly XName serverSldId = "serverSldId";
+ public static readonly XName serverSldModifiedTime = "serverSldModifiedTime";
+ public static readonly XName setDefinition = "setDefinition";
+ public static readonly XName shapeId = "shapeId";
+ public static readonly XName ShapeID = "ShapeID";
+ public static readonly XName sheet = "sheet";
+ public static readonly XName sheetId = "sheetId";
+ public static readonly XName sheetPosition = "sheetPosition";
+ public static readonly XName show = "show";
+ public static readonly XName showAll = "showAll";
+ public static readonly XName showCaptions = "showCaptions";
+ public static readonly XName showColHeaders = "showColHeaders";
+ public static readonly XName showColStripes = "showColStripes";
+ public static readonly XName showColumnStripes = "showColumnStripes";
+ public static readonly XName showErrorMessage = "showErrorMessage";
+ public static readonly XName showFirstColumn = "showFirstColumn";
+ public static readonly XName showHeader = "showHeader";
+ public static readonly XName showInputMessage = "showInputMessage";
+ public static readonly XName showLastColumn = "showLastColumn";
+ public static readonly XName showRowHeaders = "showRowHeaders";
+ public static readonly XName showRowStripes = "showRowStripes";
+ public static readonly XName showValue = "showValue";
+ public static readonly XName shrinkToFit = "shrinkToFit";
+ public static readonly XName si = "si";
+ public static readonly XName sId = "sId";
+ public static readonly XName simplePos = "simplePos";
+ public static readonly XName size = "size";
+ public static readonly XName skewangle = "skewangle";
+ public static readonly XName smoothness = "smoothness";
+ public static readonly XName smtClean = "smtClean";
+ public static readonly XName source = "source";
+ public static readonly XName sourceFile = "sourceFile";
+ public static readonly XName SourceId = "SourceId";
+ public static readonly XName sourceLinked = "sourceLinked";
+ public static readonly XName sourceSheetId = "sourceSheetId";
+ public static readonly XName sourceType = "sourceType";
+ public static readonly XName sp = "sp";
+ public static readonly XName spans = "spans";
+ public static readonly XName spcCol = "spcCol";
+ public static readonly XName spcFirstLastPara = "spcFirstLastPara";
+ public static readonly XName spid = "spid";
+ public static readonly XName spidmax = "spidmax";
+ public static readonly XName spinCount = "spinCount";
+ public static readonly XName splitFirst = "splitFirst";
+ public static readonly XName spokes = "spokes";
+ public static readonly XName sqlType = "sqlType";
+ public static readonly XName sqref = "sqref";
+ public static readonly XName src = "src";
+ public static readonly XName srcId = "srcId";
+ public static readonly XName srcOrd = "srcOrd";
+ public static readonly XName st = "st";
+ public static readonly XName stA = "stA";
+ public static readonly XName stAng = "stAng";
+ public static readonly XName start = "start";
+ public static readonly XName startangle = "startangle";
+ public static readonly XName startDate = "startDate";
+ public static readonly XName status = "status";
+ public static readonly XName strike = "strike";
+ public static readonly XName _string = "string";
+ public static readonly XName strokecolor = "strokecolor";
+ public static readonly XName stroked = "stroked";
+ public static readonly XName strokeweight = "strokeweight";
+ public static readonly XName style = "style";
+ public static readonly XName styleId = "styleId";
+ public static readonly XName styleName = "styleName";
+ public static readonly XName subtotal = "subtotal";
+ public static readonly XName summaryBelow = "summaryBelow";
+ public static readonly XName swAng = "swAng";
+ public static readonly XName sx = "sx";
+ public static readonly XName sy = "sy";
+ public static readonly XName sz = "sz";
+ public static readonly XName t = "t";
+ public static readonly XName tab = "tab";
+ public static readonly XName tableBorderDxfId = "tableBorderDxfId";
+ public static readonly XName tableColumnId = "tableColumnId";
+ public static readonly XName Target = "Target";
+ public static readonly XName textlink = "textlink";
+ public static readonly XName textRotation = "textRotation";
+ public static readonly XName theme = "theme";
+ public static readonly XName thresh = "thresh";
+ public static readonly XName thruBlk = "thruBlk";
+ public static readonly XName time = "time";
+ public static readonly XName tIns = "tIns";
+ public static readonly XName tint = "tint";
+ public static readonly XName tm = "tm";
+ public static readonly XName to = "to";
+ public static readonly XName tooltip = "tooltip";
+ public static readonly XName top = "top";
+ public static readonly XName topLabels = "topLabels";
+ public static readonly XName topLeftCell = "topLeftCell";
+ public static readonly XName totalsRowShown = "totalsRowShown";
+ public static readonly XName track = "track";
+ public static readonly XName trans = "trans";
+ public static readonly XName transition = "transition";
+ public static readonly XName trend = "trend";
+ public static readonly XName twoDigitTextYear = "twoDigitTextYear";
+ public static readonly XName tx = "tx";
+ public static readonly XName tx1 = "tx1";
+ public static readonly XName tx2 = "tx2";
+ public static readonly XName txBox = "txBox";
+ public static readonly XName txbxSeq = "txbxSeq";
+ public static readonly XName txbxStory = "txbxStory";
+ public static readonly XName ty = "ty";
+ public static readonly XName type = "type";
+ public static readonly XName Type = "Type";
+ public static readonly XName typeface = "typeface";
+ public static readonly XName u = "u";
+ public static readonly XName ua = "ua";
+ public static readonly XName uiExpand = "uiExpand";
+ public static readonly XName unbalanced = "unbalanced";
+ public static readonly XName uniqueCount = "uniqueCount";
+ public static readonly XName uniqueId = "uniqueId";
+ public static readonly XName uniqueName = "uniqueName";
+ public static readonly XName uniqueParent = "uniqueParent";
+ public static readonly XName updateAutomatic = "updateAutomatic";
+ public static readonly XName updatedVersion = "updatedVersion";
+ public static readonly XName uri = "uri";
+ public static readonly XName URI = "URI";
+ public static readonly XName url = "url";
+ public static readonly XName useAutoFormatting = "useAutoFormatting";
+ public static readonly XName useDef = "useDef";
+ public static readonly XName user = "user";
+ public static readonly XName userName = "userName";
+ public static readonly XName v = "v";
+ public static readonly XName val = "val";
+ public static readonly XName value = "value";
+ public static readonly XName valueType = "valueType";
+ public static readonly XName varScale = "varScale";
+ public static readonly XName vert = "vert";
+ public static readonly XName vertical = "vertical";
+ public static readonly XName verticalCentered = "verticalCentered";
+ public static readonly XName verticalDpi = "verticalDpi";
+ public static readonly XName vertOverflow = "vertOverflow";
+ public static readonly XName viewpoint = "viewpoint";
+ public static readonly XName viewpointorigin = "viewpointorigin";
+ public static readonly XName w = "w";
+ public static readonly XName weight = "weight";
+ public static readonly XName width = "width";
+ public static readonly XName workbookViewId = "workbookViewId";
+ public static readonly XName wR = "wR";
+ public static readonly XName wrap = "wrap";
+ public static readonly XName wrapText = "wrapText";
+ public static readonly XName x = "x";
+ public static readonly XName x1 = "x1";
+ public static readonly XName x2 = "x2";
+ public static readonly XName xfId = "xfId";
+ public static readonly XName xl97 = "xl97";
+ public static readonly XName xmlDataType = "xmlDataType";
+ public static readonly XName xpath = "xpath";
+ public static readonly XName xSplit = "xSplit";
+ public static readonly XName y = "y";
+ public static readonly XName y1 = "y1";
+ public static readonly XName y2 = "y2";
+ public static readonly XName year = "year";
+ public static readonly XName yrange = "yrange";
+ public static readonly XName ySplit = "ySplit";
+ public static readonly XName z = "z";
+ }
+
+ public static class O
+ {
+ public static readonly XNamespace o =
+ "urn:schemas-microsoft-com:office:office";
+ public static readonly XName allowincell = o + "allowincell";
+ public static readonly XName allowoverlap = o + "allowoverlap";
+ public static readonly XName althref = o + "althref";
+ public static readonly XName borderbottomcolor = o + "borderbottomcolor";
+ public static readonly XName borderleftcolor = o + "borderleftcolor";
+ public static readonly XName borderrightcolor = o + "borderrightcolor";
+ public static readonly XName bordertopcolor = o + "bordertopcolor";
+ public static readonly XName bottom = o + "bottom";
+ public static readonly XName bullet = o + "bullet";
+ public static readonly XName button = o + "button";
+ public static readonly XName bwmode = o + "bwmode";
+ public static readonly XName bwnormal = o + "bwnormal";
+ public static readonly XName bwpure = o + "bwpure";
+ public static readonly XName callout = o + "callout";
+ public static readonly XName clip = o + "clip";
+ public static readonly XName clippath = o + "clippath";
+ public static readonly XName cliptowrap = o + "cliptowrap";
+ public static readonly XName colormenu = o + "colormenu";
+ public static readonly XName colormru = o + "colormru";
+ public static readonly XName column = o + "column";
+ public static readonly XName complex = o + "complex";
+ public static readonly XName connectangles = o + "connectangles";
+ public static readonly XName connectlocs = o + "connectlocs";
+ public static readonly XName connectortype = o + "connectortype";
+ public static readonly XName connecttype = o + "connecttype";
+ public static readonly XName detectmouseclick = o + "detectmouseclick";
+ public static readonly XName dgmlayout = o + "dgmlayout";
+ public static readonly XName dgmlayoutmru = o + "dgmlayoutmru";
+ public static readonly XName dgmnodekind = o + "dgmnodekind";
+ public static readonly XName diagram = o + "diagram";
+ public static readonly XName doubleclicknotify = o + "doubleclicknotify";
+ public static readonly XName entry = o + "entry";
+ public static readonly XName extrusion = o + "extrusion";
+ public static readonly XName extrusionok = o + "extrusionok";
+ public static readonly XName FieldCodes = o + "FieldCodes";
+ public static readonly XName fill = o + "fill";
+ public static readonly XName forcedash = o + "forcedash";
+ public static readonly XName gfxdata = o + "gfxdata";
+ public static readonly XName hr = o + "hr";
+ public static readonly XName hralign = o + "hralign";
+ public static readonly XName href = o + "href";
+ public static readonly XName hrnoshade = o + "hrnoshade";
+ public static readonly XName hrpct = o + "hrpct";
+ public static readonly XName hrstd = o + "hrstd";
+ public static readonly XName idmap = o + "idmap";
+ public static readonly XName ink = o + "ink";
+ public static readonly XName insetmode = o + "insetmode";
+ public static readonly XName left = o + "left";
+ public static readonly XName LinkType = o + "LinkType";
+ public static readonly XName _lock = o + "lock";
+ public static readonly XName LockedField = o + "LockedField";
+ public static readonly XName master = o + "master";
+ public static readonly XName ole = o + "ole";
+ public static readonly XName oleicon = o + "oleicon";
+ public static readonly XName OLEObject = o + "OLEObject";
+ public static readonly XName oned = o + "oned";
+ public static readonly XName opacity2 = o + "opacity2";
+ public static readonly XName preferrelative = o + "preferrelative";
+ public static readonly XName proxy = o + "proxy";
+ public static readonly XName r = o + "r";
+ public static readonly XName regroupid = o + "regroupid";
+ public static readonly XName regrouptable = o + "regrouptable";
+ public static readonly XName rel = o + "rel";
+ public static readonly XName relationtable = o + "relationtable";
+ public static readonly XName relid = o + "relid";
+ public static readonly XName right = o + "right";
+ public static readonly XName rules = o + "rules";
+ public static readonly XName shapedefaults = o + "shapedefaults";
+ public static readonly XName shapelayout = o + "shapelayout";
+ public static readonly XName signatureline = o + "signatureline";
+ public static readonly XName singleclick = o + "singleclick";
+ public static readonly XName skew = o + "skew";
+ public static readonly XName spid = o + "spid";
+ public static readonly XName spt = o + "spt";
+ public static readonly XName suggestedsigner = o + "suggestedsigner";
+ public static readonly XName suggestedsigner2 = o + "suggestedsigner2";
+ public static readonly XName suggestedsigneremail = o + "suggestedsigneremail";
+ public static readonly XName tablelimits = o + "tablelimits";
+ public static readonly XName tableproperties = o + "tableproperties";
+ public static readonly XName targetscreensize = o + "targetscreensize";
+ public static readonly XName title = o + "title";
+ public static readonly XName top = o + "top";
+ public static readonly XName userdrawn = o + "userdrawn";
+ public static readonly XName userhidden = o + "userhidden";
+ public static readonly XName v = o + "v";
+ }
+
+ public static class P
+ {
+ public static readonly XNamespace p =
+ "http://schemas.openxmlformats.org/presentationml/2006/main";
+ public static readonly XName anim = p + "anim";
+ public static readonly XName animClr = p + "animClr";
+ public static readonly XName animEffect = p + "animEffect";
+ public static readonly XName animMotion = p + "animMotion";
+ public static readonly XName animRot = p + "animRot";
+ public static readonly XName animScale = p + "animScale";
+ public static readonly XName attrName = p + "attrName";
+ public static readonly XName attrNameLst = p + "attrNameLst";
+ public static readonly XName audio = p + "audio";
+ public static readonly XName bg = p + "bg";
+ public static readonly XName bgPr = p + "bgPr";
+ public static readonly XName bgRef = p + "bgRef";
+ public static readonly XName bldAsOne = p + "bldAsOne";
+ public static readonly XName bldDgm = p + "bldDgm";
+ public static readonly XName bldGraphic = p + "bldGraphic";
+ public static readonly XName bldLst = p + "bldLst";
+ public static readonly XName bldOleChart = p + "bldOleChart";
+ public static readonly XName bldP = p + "bldP";
+ public static readonly XName bldSub = p + "bldSub";
+ public static readonly XName blinds = p + "blinds";
+ public static readonly XName blipFill = p + "blipFill";
+ public static readonly XName bodyStyle = p + "bodyStyle";
+ public static readonly XName bold = p + "bold";
+ public static readonly XName boldItalic = p + "boldItalic";
+ public static readonly XName boolVal = p + "boolVal";
+ public static readonly XName by = p + "by";
+ public static readonly XName cBhvr = p + "cBhvr";
+ public static readonly XName charRg = p + "charRg";
+ public static readonly XName checker = p + "checker";
+ public static readonly XName childTnLst = p + "childTnLst";
+ public static readonly XName circle = p + "circle";
+ public static readonly XName clrMap = p + "clrMap";
+ public static readonly XName clrMapOvr = p + "clrMapOvr";
+ public static readonly XName clrVal = p + "clrVal";
+ public static readonly XName cm = p + "cm";
+ public static readonly XName cmAuthor = p + "cmAuthor";
+ public static readonly XName cmAuthorLst = p + "cmAuthorLst";
+ public static readonly XName cmd = p + "cmd";
+ public static readonly XName cMediaNode = p + "cMediaNode";
+ public static readonly XName cmLst = p + "cmLst";
+ public static readonly XName cNvCxnSpPr = p + "cNvCxnSpPr";
+ public static readonly XName cNvGraphicFramePr = p + "cNvGraphicFramePr";
+ public static readonly XName cNvGrpSpPr = p + "cNvGrpSpPr";
+ public static readonly XName cNvPicPr = p + "cNvPicPr";
+ public static readonly XName cNvPr = p + "cNvPr";
+ public static readonly XName cNvSpPr = p + "cNvSpPr";
+ public static readonly XName comb = p + "comb";
+ public static readonly XName cond = p + "cond";
+ public static readonly XName contentPart = p + "contentPart";
+ public static readonly XName control = p + "control";
+ public static readonly XName controls = p + "controls";
+ public static readonly XName cover = p + "cover";
+ public static readonly XName cSld = p + "cSld";
+ public static readonly XName cSldViewPr = p + "cSldViewPr";
+ public static readonly XName cTn = p + "cTn";
+ public static readonly XName custData = p + "custData";
+ public static readonly XName custDataLst = p + "custDataLst";
+ public static readonly XName custShow = p + "custShow";
+ public static readonly XName custShowLst = p + "custShowLst";
+ public static readonly XName cut = p + "cut";
+ public static readonly XName cViewPr = p + "cViewPr";
+ public static readonly XName cxnSp = p + "cxnSp";
+ public static readonly XName defaultTextStyle = p + "defaultTextStyle";
+ public static readonly XName diamond = p + "diamond";
+ public static readonly XName dissolve = p + "dissolve";
+ public static readonly XName embed = p + "embed";
+ public static readonly XName embeddedFont = p + "embeddedFont";
+ public static readonly XName embeddedFontLst = p + "embeddedFontLst";
+ public static readonly XName endCondLst = p + "endCondLst";
+ public static readonly XName endSnd = p + "endSnd";
+ public static readonly XName endSync = p + "endSync";
+ public static readonly XName ext = p + "ext";
+ public static readonly XName externalData = p + "externalData";
+ public static readonly XName extLst = p + "extLst";
+ public static readonly XName fade = p + "fade";
+ public static readonly XName fltVal = p + "fltVal";
+ public static readonly XName font = p + "font";
+ public static readonly XName from = p + "from";
+ public static readonly XName graphicEl = p + "graphicEl";
+ public static readonly XName graphicFrame = p + "graphicFrame";
+ public static readonly XName gridSpacing = p + "gridSpacing";
+ public static readonly XName grpSp = p + "grpSp";
+ public static readonly XName grpSpPr = p + "grpSpPr";
+ public static readonly XName guide = p + "guide";
+ public static readonly XName guideLst = p + "guideLst";
+ public static readonly XName handoutMaster = p + "handoutMaster";
+ public static readonly XName handoutMasterId = p + "handoutMasterId";
+ public static readonly XName handoutMasterIdLst = p + "handoutMasterIdLst";
+ public static readonly XName hf = p + "hf";
+ public static readonly XName hsl = p + "hsl";
+ public static readonly XName inkTgt = p + "inkTgt";
+ public static readonly XName italic = p + "italic";
+ public static readonly XName iterate = p + "iterate";
+ public static readonly XName kinsoku = p + "kinsoku";
+ public static readonly XName link = p + "link";
+ public static readonly XName modifyVerifier = p + "modifyVerifier";
+ public static readonly XName newsflash = p + "newsflash";
+ public static readonly XName nextCondLst = p + "nextCondLst";
+ public static readonly XName normalViewPr = p + "normalViewPr";
+ public static readonly XName notes = p + "notes";
+ public static readonly XName notesMaster = p + "notesMaster";
+ public static readonly XName notesMasterId = p + "notesMasterId";
+ public static readonly XName notesMasterIdLst = p + "notesMasterIdLst";
+ public static readonly XName notesStyle = p + "notesStyle";
+ public static readonly XName notesSz = p + "notesSz";
+ public static readonly XName notesTextViewPr = p + "notesTextViewPr";
+ public static readonly XName notesViewPr = p + "notesViewPr";
+ public static readonly XName nvCxnSpPr = p + "nvCxnSpPr";
+ public static readonly XName nvGraphicFramePr = p + "nvGraphicFramePr";
+ public static readonly XName nvGrpSpPr = p + "nvGrpSpPr";
+ public static readonly XName nvPicPr = p + "nvPicPr";
+ public static readonly XName nvPr = p + "nvPr";
+ public static readonly XName nvSpPr = p + "nvSpPr";
+ public static readonly XName oleChartEl = p + "oleChartEl";
+ public static readonly XName oleObj = p + "oleObj";
+ public static readonly XName origin = p + "origin";
+ public static readonly XName otherStyle = p + "otherStyle";
+ public static readonly XName outlineViewPr = p + "outlineViewPr";
+ public static readonly XName par = p + "par";
+ public static readonly XName ph = p + "ph";
+ public static readonly XName photoAlbum = p + "photoAlbum";
+ public static readonly XName pic = p + "pic";
+ public static readonly XName plus = p + "plus";
+ public static readonly XName pos = p + "pos";
+ public static readonly XName presentation = p + "presentation";
+ public static readonly XName prevCondLst = p + "prevCondLst";
+ public static readonly XName pRg = p + "pRg";
+ public static readonly XName pull = p + "pull";
+ public static readonly XName push = p + "push";
+ public static readonly XName random = p + "random";
+ public static readonly XName randomBar = p + "randomBar";
+ public static readonly XName rCtr = p + "rCtr";
+ public static readonly XName regular = p + "regular";
+ public static readonly XName restoredLeft = p + "restoredLeft";
+ public static readonly XName restoredTop = p + "restoredTop";
+ public static readonly XName rgb = p + "rgb";
+ public static readonly XName rtn = p + "rtn";
+ public static readonly XName scale = p + "scale";
+ public static readonly XName seq = p + "seq";
+ public static readonly XName set = p + "set";
+ public static readonly XName sld = p + "sld";
+ public static readonly XName sldId = p + "sldId";
+ public static readonly XName sldIdLst = p + "sldIdLst";
+ public static readonly XName sldLayout = p + "sldLayout";
+ public static readonly XName sldLayoutId = p + "sldLayoutId";
+ public static readonly XName sldLayoutIdLst = p + "sldLayoutIdLst";
+ public static readonly XName sldLst = p + "sldLst";
+ public static readonly XName sldMaster = p + "sldMaster";
+ public static readonly XName sldMasterId = p + "sldMasterId";
+ public static readonly XName sldMasterIdLst = p + "sldMasterIdLst";
+ public static readonly XName sldSyncPr = p + "sldSyncPr";
+ public static readonly XName sldSz = p + "sldSz";
+ public static readonly XName sldTgt = p + "sldTgt";
+ public static readonly XName slideViewPr = p + "slideViewPr";
+ public static readonly XName snd = p + "snd";
+ public static readonly XName sndAc = p + "sndAc";
+ public static readonly XName sndTgt = p + "sndTgt";
+ public static readonly XName sorterViewPr = p + "sorterViewPr";
+ public static readonly XName sp = p + "sp";
+ public static readonly XName split = p + "split";
+ public static readonly XName spPr = p + "spPr";
+ public static readonly XName spTgt = p + "spTgt";
+ public static readonly XName spTree = p + "spTree";
+ public static readonly XName stCondLst = p + "stCondLst";
+ public static readonly XName strips = p + "strips";
+ public static readonly XName strVal = p + "strVal";
+ public static readonly XName stSnd = p + "stSnd";
+ public static readonly XName style = p + "style";
+ public static readonly XName subSp = p + "subSp";
+ public static readonly XName subTnLst = p + "subTnLst";
+ public static readonly XName tag = p + "tag";
+ public static readonly XName tagLst = p + "tagLst";
+ public static readonly XName tags = p + "tags";
+ public static readonly XName tav = p + "tav";
+ public static readonly XName tavLst = p + "tavLst";
+ public static readonly XName text = p + "text";
+ public static readonly XName tgtEl = p + "tgtEl";
+ public static readonly XName timing = p + "timing";
+ public static readonly XName titleStyle = p + "titleStyle";
+ public static readonly XName tmAbs = p + "tmAbs";
+ public static readonly XName tmPct = p + "tmPct";
+ public static readonly XName tmpl = p + "tmpl";
+ public static readonly XName tmplLst = p + "tmplLst";
+ public static readonly XName tn = p + "tn";
+ public static readonly XName tnLst = p + "tnLst";
+ public static readonly XName to = p + "to";
+ public static readonly XName transition = p + "transition";
+ public static readonly XName txBody = p + "txBody";
+ public static readonly XName txEl = p + "txEl";
+ public static readonly XName txStyles = p + "txStyles";
+ public static readonly XName val = p + "val";
+ public static readonly XName video = p + "video";
+ public static readonly XName viewPr = p + "viewPr";
+ public static readonly XName wedge = p + "wedge";
+ public static readonly XName wheel = p + "wheel";
+ public static readonly XName wipe = p + "wipe";
+ public static readonly XName xfrm = p + "xfrm";
+ public static readonly XName zoom = p + "zoom";
+ }
+
+ public static class P14
+ {
+ public static readonly XNamespace p14 =
+ "http://schemas.microsoft.com/office/powerpoint/2010/main";
+ public static readonly XName bmk = p14 + "bmk";
+ public static readonly XName bmkLst = p14 + "bmkLst";
+ public static readonly XName bmkTgt = p14 + "bmkTgt";
+ public static readonly XName bounceEnd = p14 + "bounceEnd";
+ public static readonly XName bwMode = p14 + "bwMode";
+ public static readonly XName cNvContentPartPr = p14 + "cNvContentPartPr";
+ public static readonly XName cNvPr = p14 + "cNvPr";
+ public static readonly XName conveyor = p14 + "conveyor";
+ public static readonly XName creationId = p14 + "creationId";
+ public static readonly XName doors = p14 + "doors";
+ public static readonly XName dur = p14 + "dur";
+ public static readonly XName extLst = p14 + "extLst";
+ public static readonly XName fade = p14 + "fade";
+ public static readonly XName ferris = p14 + "ferris";
+ public static readonly XName flash = p14 + "flash";
+ public static readonly XName flip = p14 + "flip";
+ public static readonly XName flythrough = p14 + "flythrough";
+ public static readonly XName gallery = p14 + "gallery";
+ public static readonly XName glitter = p14 + "glitter";
+ public static readonly XName honeycomb = p14 + "honeycomb";
+ public static readonly XName laserTraceLst = p14 + "laserTraceLst";
+ public static readonly XName media = p14 + "media";
+ public static readonly XName modId = p14 + "modId";
+ public static readonly XName nvContentPartPr = p14 + "nvContentPartPr";
+ public static readonly XName nvPr = p14 + "nvPr";
+ public static readonly XName pan = p14 + "pan";
+ public static readonly XName pauseEvt = p14 + "pauseEvt";
+ public static readonly XName playEvt = p14 + "playEvt";
+ public static readonly XName presetBounceEnd = p14 + "presetBounceEnd";
+ public static readonly XName prism = p14 + "prism";
+ public static readonly XName resumeEvt = p14 + "resumeEvt";
+ public static readonly XName reveal = p14 + "reveal";
+ public static readonly XName ripple = p14 + "ripple";
+ public static readonly XName section = p14 + "section";
+ public static readonly XName sectionLst = p14 + "sectionLst";
+ public static readonly XName seekEvt = p14 + "seekEvt";
+ public static readonly XName showEvtLst = p14 + "showEvtLst";
+ public static readonly XName shred = p14 + "shred";
+ public static readonly XName sldId = p14 + "sldId";
+ public static readonly XName sldIdLst = p14 + "sldIdLst";
+ public static readonly XName stopEvt = p14 + "stopEvt";
+ public static readonly XName _switch = p14 + "switch";
+ public static readonly XName tracePt = p14 + "tracePt";
+ public static readonly XName tracePtLst = p14 + "tracePtLst";
+ public static readonly XName triggerEvt = p14 + "triggerEvt";
+ public static readonly XName trim = p14 + "trim";
+ public static readonly XName vortex = p14 + "vortex";
+ public static readonly XName warp = p14 + "warp";
+ public static readonly XName wheelReverse = p14 + "wheelReverse";
+ public static readonly XName window = p14 + "window";
+ public static readonly XName xfrm = p14 + "xfrm";
+ }
+
+ public static class P15
+ {
+ public static readonly XNamespace p15 =
+ "http://schemas.microsoft.com/office15/powerpoint";
+ public static readonly XName extElement = p15 + "extElement";
+ }
+
+ public static class PAV
+ {
+ public static readonly XNamespace pav = "http://schemas.microsoft.com/office/2007/6/19/audiovideo";
+ public static readonly XName media = pav + "media";
+ public static readonly XName srcMedia = pav + "srcMedia";
+ public static readonly XName bmkLst = pav + "bmkLst";
+ }
+
+ public static class Pic
+ {
+ public static readonly XNamespace pic =
+ "http://schemas.openxmlformats.org/drawingml/2006/picture";
+ public static readonly XName blipFill = pic + "blipFill";
+ public static readonly XName cNvPicPr = pic + "cNvPicPr";
+ public static readonly XName cNvPr = pic + "cNvPr";
+ public static readonly XName nvPicPr = pic + "nvPicPr";
+ public static readonly XName _pic = pic + "pic";
+ public static readonly XName spPr = pic + "spPr";
+ }
+
+ public static class Plegacy
+ {
+ public static readonly XNamespace plegacy = "urn:schemas-microsoft-com:office:powerpoint";
+ public static readonly XName textdata = plegacy + "textdata";
+ }
+
+ public static class R
+ {
+ public static readonly XNamespace r =
+ "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
+ public static readonly XName blip = r + "blip";
+ public static readonly XName cs = r + "cs";
+ public static readonly XName dm = r + "dm";
+ public static readonly XName embed = r + "embed";
+ public static readonly XName href = r + "href";
+ public static readonly XName id = r + "id";
+ public static readonly XName link = r + "link";
+ public static readonly XName lo = r + "lo";
+ public static readonly XName pict = r + "pict";
+ public static readonly XName qs = r + "qs";
+ public static readonly XName verticalDpi = r + "verticalDpi";
+ }
+
+ public static class S
+ {
+ public static readonly XNamespace s =
+ "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ public static readonly XName alignment = s + "alignment";
+ public static readonly XName anchor = s + "anchor";
+ public static readonly XName author = s + "author";
+ public static readonly XName authors = s + "authors";
+ public static readonly XName autoFilter = s + "autoFilter";
+ public static readonly XName autoSortScope = s + "autoSortScope";
+ public static readonly XName b = s + "b";
+ public static readonly XName bgColor = s + "bgColor";
+ public static readonly XName bk = s + "bk";
+ public static readonly XName border = s + "border";
+ public static readonly XName borders = s + "borders";
+ public static readonly XName bottom = s + "bottom";
+ public static readonly XName brk = s + "brk";
+ public static readonly XName c = s + "c";
+ public static readonly XName cacheField = s + "cacheField";
+ public static readonly XName cacheFields = s + "cacheFields";
+ public static readonly XName cacheHierarchies = s + "cacheHierarchies";
+ public static readonly XName cacheHierarchy = s + "cacheHierarchy";
+ public static readonly XName cacheSource = s + "cacheSource";
+ public static readonly XName calcChain = s + "calcChain";
+ public static readonly XName calcPr = s + "calcPr";
+ public static readonly XName calculatedColumnFormula = s + "calculatedColumnFormula";
+ public static readonly XName calculatedItem = s + "calculatedItem";
+ public static readonly XName calculatedItems = s + "calculatedItems";
+ public static readonly XName calculatedMember = s + "calculatedMember";
+ public static readonly XName calculatedMembers = s + "calculatedMembers";
+ public static readonly XName cell = s + "cell";
+ public static readonly XName cellMetadata = s + "cellMetadata";
+ public static readonly XName cellSmartTag = s + "cellSmartTag";
+ public static readonly XName cellSmartTagPr = s + "cellSmartTagPr";
+ public static readonly XName cellSmartTags = s + "cellSmartTags";
+ public static readonly XName cellStyle = s + "cellStyle";
+ public static readonly XName cellStyles = s + "cellStyles";
+ public static readonly XName cellStyleXfs = s + "cellStyleXfs";
+ public static readonly XName cellWatch = s + "cellWatch";
+ public static readonly XName cellWatches = s + "cellWatches";
+ public static readonly XName cellXfs = s + "cellXfs";
+ public static readonly XName cfRule = s + "cfRule";
+ public static readonly XName cfvo = s + "cfvo";
+ public static readonly XName charset = s + "charset";
+ public static readonly XName chartFormat = s + "chartFormat";
+ public static readonly XName chartFormats = s + "chartFormats";
+ public static readonly XName chartsheet = s + "chartsheet";
+ public static readonly XName col = s + "col";
+ public static readonly XName colBreaks = s + "colBreaks";
+ public static readonly XName colFields = s + "colFields";
+ public static readonly XName colHierarchiesUsage = s + "colHierarchiesUsage";
+ public static readonly XName colHierarchyUsage = s + "colHierarchyUsage";
+ public static readonly XName colItems = s + "colItems";
+ public static readonly XName color = s + "color";
+ public static readonly XName colorFilter = s + "colorFilter";
+ public static readonly XName colors = s + "colors";
+ public static readonly XName colorScale = s + "colorScale";
+ public static readonly XName cols = s + "cols";
+ public static readonly XName comment = s + "comment";
+ public static readonly XName commentList = s + "commentList";
+ public static readonly XName comments = s + "comments";
+ public static readonly XName condense = s + "condense";
+ public static readonly XName conditionalFormat = s + "conditionalFormat";
+ public static readonly XName conditionalFormats = s + "conditionalFormats";
+ public static readonly XName conditionalFormatting = s + "conditionalFormatting";
+ public static readonly XName connection = s + "connection";
+ public static readonly XName connections = s + "connections";
+ public static readonly XName consolidation = s + "consolidation";
+ public static readonly XName control = s + "control";
+ public static readonly XName controlPr = s + "controlPr";
+ public static readonly XName controls = s + "controls";
+ public static readonly XName customFilter = s + "customFilter";
+ public static readonly XName customFilters = s + "customFilters";
+ public static readonly XName customPr = s + "customPr";
+ public static readonly XName customProperties = s + "customProperties";
+ public static readonly XName customSheetView = s + "customSheetView";
+ public static readonly XName customSheetViews = s + "customSheetViews";
+ public static readonly XName d = s + "d";
+ public static readonly XName dataBar = s + "dataBar";
+ public static readonly XName dataConsolidate = s + "dataConsolidate";
+ public static readonly XName dataField = s + "dataField";
+ public static readonly XName dataFields = s + "dataFields";
+ public static readonly XName dataRef = s + "dataRef";
+ public static readonly XName dataRefs = s + "dataRefs";
+ public static readonly XName dataValidation = s + "dataValidation";
+ public static readonly XName dataValidations = s + "dataValidations";
+ public static readonly XName dateGroupItem = s + "dateGroupItem";
+ public static readonly XName dbPr = s + "dbPr";
+ public static readonly XName ddeItem = s + "ddeItem";
+ public static readonly XName ddeItems = s + "ddeItems";
+ public static readonly XName ddeLink = s + "ddeLink";
+ public static readonly XName definedName = s + "definedName";
+ public static readonly XName definedNames = s + "definedNames";
+ public static readonly XName deletedField = s + "deletedField";
+ public static readonly XName diagonal = s + "diagonal";
+ public static readonly XName dialogsheet = s + "dialogsheet";
+ public static readonly XName dimension = s + "dimension";
+ public static readonly XName dimensions = s + "dimensions";
+ public static readonly XName discretePr = s + "discretePr";
+ public static readonly XName drawing = s + "drawing";
+ public static readonly XName dxf = s + "dxf";
+ public static readonly XName dxfs = s + "dxfs";
+ public static readonly XName dynamicFilter = s + "dynamicFilter";
+ public static readonly XName e = s + "e";
+ public static readonly XName entries = s + "entries";
+ public static readonly XName evenFooter = s + "evenFooter";
+ public static readonly XName evenHeader = s + "evenHeader";
+ public static readonly XName ext = s + "ext";
+ public static readonly XName extend = s + "extend";
+ public static readonly XName externalBook = s + "externalBook";
+ public static readonly XName externalLink = s + "externalLink";
+ public static readonly XName extLst = s + "extLst";
+ public static readonly XName f = s + "f";
+ public static readonly XName family = s + "family";
+ public static readonly XName fgColor = s + "fgColor";
+ public static readonly XName field = s + "field";
+ public static readonly XName fieldGroup = s + "fieldGroup";
+ public static readonly XName fieldsUsage = s + "fieldsUsage";
+ public static readonly XName fieldUsage = s + "fieldUsage";
+ public static readonly XName fill = s + "fill";
+ public static readonly XName fills = s + "fills";
+ public static readonly XName filter = s + "filter";
+ public static readonly XName filterColumn = s + "filterColumn";
+ public static readonly XName filters = s + "filters";
+ public static readonly XName firstFooter = s + "firstFooter";
+ public static readonly XName firstHeader = s + "firstHeader";
+ public static readonly XName font = s + "font";
+ public static readonly XName fonts = s + "fonts";
+ public static readonly XName foo = s + "foo";
+ public static readonly XName format = s + "format";
+ public static readonly XName formats = s + "formats";
+ public static readonly XName formula = s + "formula";
+ public static readonly XName formula1 = s + "formula1";
+ public static readonly XName formula2 = s + "formula2";
+ public static readonly XName from = s + "from";
+ public static readonly XName futureMetadata = s + "futureMetadata";
+ public static readonly XName gradientFill = s + "gradientFill";
+ public static readonly XName group = s + "group";
+ public static readonly XName groupItems = s + "groupItems";
+ public static readonly XName groupLevel = s + "groupLevel";
+ public static readonly XName groupLevels = s + "groupLevels";
+ public static readonly XName groupMember = s + "groupMember";
+ public static readonly XName groupMembers = s + "groupMembers";
+ public static readonly XName groups = s + "groups";
+ public static readonly XName header = s + "header";
+ public static readonly XName headerFooter = s + "headerFooter";
+ public static readonly XName headers = s + "headers";
+ public static readonly XName horizontal = s + "horizontal";
+ public static readonly XName hyperlink = s + "hyperlink";
+ public static readonly XName hyperlinks = s + "hyperlinks";
+ public static readonly XName i = s + "i";
+ public static readonly XName iconFilter = s + "iconFilter";
+ public static readonly XName iconSet = s + "iconSet";
+ public static readonly XName ignoredError = s + "ignoredError";
+ public static readonly XName ignoredErrors = s + "ignoredErrors";
+ public static readonly XName indexedColors = s + "indexedColors";
+ public static readonly XName inputCells = s + "inputCells";
+ public static readonly XName _is = s + "is";
+ public static readonly XName item = s + "item";
+ public static readonly XName items = s + "items";
+ public static readonly XName k = s + "k";
+ public static readonly XName kpi = s + "kpi";
+ public static readonly XName kpis = s + "kpis";
+ public static readonly XName left = s + "left";
+ public static readonly XName legacyDrawing = s + "legacyDrawing";
+ public static readonly XName legacyDrawingHF = s + "legacyDrawingHF";
+ public static readonly XName location = s + "location";
+ public static readonly XName m = s + "m";
+ public static readonly XName main = s + "main";
+ public static readonly XName map = s + "map";
+ public static readonly XName maps = s + "maps";
+ public static readonly XName mdx = s + "mdx";
+ public static readonly XName mdxMetadata = s + "mdxMetadata";
+ public static readonly XName measureGroup = s + "measureGroup";
+ public static readonly XName measureGroups = s + "measureGroups";
+ public static readonly XName member = s + "member";
+ public static readonly XName members = s + "members";
+ public static readonly XName mergeCell = s + "mergeCell";
+ public static readonly XName mergeCells = s + "mergeCells";
+ public static readonly XName metadata = s + "metadata";
+ public static readonly XName metadataStrings = s + "metadataStrings";
+ public static readonly XName metadataType = s + "metadataType";
+ public static readonly XName metadataTypes = s + "metadataTypes";
+ public static readonly XName mp = s + "mp";
+ public static readonly XName mpMap = s + "mpMap";
+ public static readonly XName mps = s + "mps";
+ public static readonly XName mruColors = s + "mruColors";
+ public static readonly XName ms = s + "ms";
+ public static readonly XName n = s + "n";
+ public static readonly XName name = s + "name";
+ public static readonly XName nc = s + "nc";
+ public static readonly XName ndxf = s + "ndxf";
+ public static readonly XName numFmt = s + "numFmt";
+ public static readonly XName numFmts = s + "numFmts";
+ public static readonly XName objectPr = s + "objectPr";
+ public static readonly XName oc = s + "oc";
+ public static readonly XName oddFooter = s + "oddFooter";
+ public static readonly XName oddHeader = s + "oddHeader";
+ public static readonly XName odxf = s + "odxf";
+ public static readonly XName olapPr = s + "olapPr";
+ public static readonly XName oldFormula = s + "oldFormula";
+ public static readonly XName oleItem = s + "oleItem";
+ public static readonly XName oleItems = s + "oleItems";
+ public static readonly XName oleLink = s + "oleLink";
+ public static readonly XName oleObject = s + "oleObject";
+ public static readonly XName oleObjects = s + "oleObjects";
+ public static readonly XName outline = s + "outline";
+ public static readonly XName outlinePr = s + "outlinePr";
+ public static readonly XName p = s + "p";
+ public static readonly XName page = s + "page";
+ public static readonly XName pageField = s + "pageField";
+ public static readonly XName pageFields = s + "pageFields";
+ public static readonly XName pageItem = s + "pageItem";
+ public static readonly XName pageMargins = s + "pageMargins";
+ public static readonly XName pages = s + "pages";
+ public static readonly XName pageSetup = s + "pageSetup";
+ public static readonly XName pageSetUpPr = s + "pageSetUpPr";
+ public static readonly XName pane = s + "pane";
+ public static readonly XName parameter = s + "parameter";
+ public static readonly XName parameters = s + "parameters";
+ public static readonly XName patternFill = s + "patternFill";
+ public static readonly XName phoneticPr = s + "phoneticPr";
+ public static readonly XName picture = s + "picture";
+ public static readonly XName pivotArea = s + "pivotArea";
+ public static readonly XName pivotAreas = s + "pivotAreas";
+ public static readonly XName pivotCache = s + "pivotCache";
+ public static readonly XName pivotCacheDefinition = s + "pivotCacheDefinition";
+ public static readonly XName pivotCacheRecords = s + "pivotCacheRecords";
+ public static readonly XName pivotCaches = s + "pivotCaches";
+ public static readonly XName pivotField = s + "pivotField";
+ public static readonly XName pivotFields = s + "pivotFields";
+ public static readonly XName pivotHierarchies = s + "pivotHierarchies";
+ public static readonly XName pivotHierarchy = s + "pivotHierarchy";
+ public static readonly XName pivotSelection = s + "pivotSelection";
+ public static readonly XName pivotTableDefinition = s + "pivotTableDefinition";
+ public static readonly XName pivotTableStyleInfo = s + "pivotTableStyleInfo";
+ public static readonly XName printOptions = s + "printOptions";
+ public static readonly XName protectedRange = s + "protectedRange";
+ public static readonly XName protectedRanges = s + "protectedRanges";
+ public static readonly XName protection = s + "protection";
+ public static readonly XName query = s + "query";
+ public static readonly XName queryCache = s + "queryCache";
+ public static readonly XName queryTable = s + "queryTable";
+ public static readonly XName queryTableDeletedFields = s + "queryTableDeletedFields";
+ public static readonly XName queryTableField = s + "queryTableField";
+ public static readonly XName queryTableFields = s + "queryTableFields";
+ public static readonly XName queryTableRefresh = s + "queryTableRefresh";
+ public static readonly XName r = s + "r";
+ public static readonly XName raf = s + "raf";
+ public static readonly XName rangePr = s + "rangePr";
+ public static readonly XName rangeSet = s + "rangeSet";
+ public static readonly XName rangeSets = s + "rangeSets";
+ public static readonly XName rc = s + "rc";
+ public static readonly XName rcc = s + "rcc";
+ public static readonly XName rcft = s + "rcft";
+ public static readonly XName rcmt = s + "rcmt";
+ public static readonly XName rcv = s + "rcv";
+ public static readonly XName rdn = s + "rdn";
+ public static readonly XName reference = s + "reference";
+ public static readonly XName references = s + "references";
+ public static readonly XName reviewed = s + "reviewed";
+ public static readonly XName reviewedList = s + "reviewedList";
+ public static readonly XName revisions = s + "revisions";
+ public static readonly XName rfmt = s + "rfmt";
+ public static readonly XName rFont = s + "rFont";
+ public static readonly XName rgbColor = s + "rgbColor";
+ public static readonly XName right = s + "right";
+ public static readonly XName ris = s + "ris";
+ public static readonly XName rm = s + "rm";
+ public static readonly XName row = s + "row";
+ public static readonly XName rowBreaks = s + "rowBreaks";
+ public static readonly XName rowFields = s + "rowFields";
+ public static readonly XName rowHierarchiesUsage = s + "rowHierarchiesUsage";
+ public static readonly XName rowHierarchyUsage = s + "rowHierarchyUsage";
+ public static readonly XName rowItems = s + "rowItems";
+ public static readonly XName rPh = s + "rPh";
+ public static readonly XName rPr = s + "rPr";
+ public static readonly XName rqt = s + "rqt";
+ public static readonly XName rrc = s + "rrc";
+ public static readonly XName rsnm = s + "rsnm";
+ public static readonly XName _s = s + "s";
+ public static readonly XName scenario = s + "scenario";
+ public static readonly XName scenarios = s + "scenarios";
+ public static readonly XName scheme = s + "scheme";
+ public static readonly XName selection = s + "selection";
+ public static readonly XName serverFormat = s + "serverFormat";
+ public static readonly XName serverFormats = s + "serverFormats";
+ public static readonly XName set = s + "set";
+ public static readonly XName sets = s + "sets";
+ public static readonly XName shadow = s + "shadow";
+ public static readonly XName sharedItems = s + "sharedItems";
+ public static readonly XName sheet = s + "sheet";
+ public static readonly XName sheetCalcPr = s + "sheetCalcPr";
+ public static readonly XName sheetData = s + "sheetData";
+ public static readonly XName sheetDataSet = s + "sheetDataSet";
+ public static readonly XName sheetFormatPr = s + "sheetFormatPr";
+ public static readonly XName sheetId = s + "sheetId";
+ public static readonly XName sheetIdMap = s + "sheetIdMap";
+ public static readonly XName sheetName = s + "sheetName";
+ public static readonly XName sheetNames = s + "sheetNames";
+ public static readonly XName sheetPr = s + "sheetPr";
+ public static readonly XName sheetProtection = s + "sheetProtection";
+ public static readonly XName sheets = s + "sheets";
+ public static readonly XName sheetView = s + "sheetView";
+ public static readonly XName sheetViews = s + "sheetViews";
+ public static readonly XName si = s + "si";
+ public static readonly XName singleXmlCell = s + "singleXmlCell";
+ public static readonly XName singleXmlCells = s + "singleXmlCells";
+ public static readonly XName smartTags = s + "smartTags";
+ public static readonly XName sortByTuple = s + "sortByTuple";
+ public static readonly XName sortCondition = s + "sortCondition";
+ public static readonly XName sortState = s + "sortState";
+ public static readonly XName sst = s + "sst";
+ public static readonly XName stop = s + "stop";
+ public static readonly XName stp = s + "stp";
+ public static readonly XName strike = s + "strike";
+ public static readonly XName styleSheet = s + "styleSheet";
+ public static readonly XName sz = s + "sz";
+ public static readonly XName t = s + "t";
+ public static readonly XName tabColor = s + "tabColor";
+ public static readonly XName table = s + "table";
+ public static readonly XName tableColumn = s + "tableColumn";
+ public static readonly XName tableColumns = s + "tableColumns";
+ public static readonly XName tablePart = s + "tablePart";
+ public static readonly XName tableParts = s + "tableParts";
+ public static readonly XName tables = s + "tables";
+ public static readonly XName tableStyle = s + "tableStyle";
+ public static readonly XName tableStyleElement = s + "tableStyleElement";
+ public static readonly XName tableStyleInfo = s + "tableStyleInfo";
+ public static readonly XName tableStyles = s + "tableStyles";
+ public static readonly XName text = s + "text";
+ public static readonly XName textField = s + "textField";
+ public static readonly XName textFields = s + "textFields";
+ public static readonly XName textPr = s + "textPr";
+ public static readonly XName to = s + "to";
+ public static readonly XName top = s + "top";
+ public static readonly XName top10 = s + "top10";
+ public static readonly XName totalsRowFormula = s + "totalsRowFormula";
+ public static readonly XName tp = s + "tp";
+ public static readonly XName tpl = s + "tpl";
+ public static readonly XName tpls = s + "tpls";
+ public static readonly XName tr = s + "tr";
+ public static readonly XName tupleCache = s + "tupleCache";
+ public static readonly XName u = s + "u";
+ public static readonly XName undo = s + "undo";
+ public static readonly XName userInfo = s + "userInfo";
+ public static readonly XName users = s + "users";
+ public static readonly XName v = s + "v";
+ public static readonly XName val = s + "val";
+ public static readonly XName value = s + "value";
+ public static readonly XName valueMetadata = s + "valueMetadata";
+ public static readonly XName values = s + "values";
+ public static readonly XName vertAlign = s + "vertAlign";
+ public static readonly XName vertical = s + "vertical";
+ public static readonly XName volType = s + "volType";
+ public static readonly XName volTypes = s + "volTypes";
+ public static readonly XName webPr = s + "webPr";
+ public static readonly XName webPublishItem = s + "webPublishItem";
+ public static readonly XName webPublishItems = s + "webPublishItems";
+ public static readonly XName worksheet = s + "worksheet";
+ public static readonly XName worksheetEx14 = s + "worksheetEx14";
+ public static readonly XName worksheetSource = s + "worksheetSource";
+ public static readonly XName x = s + "x";
+ public static readonly XName xf = s + "xf";
+ public static readonly XName xmlCellPr = s + "xmlCellPr";
+ public static readonly XName xmlColumnPr = s + "xmlColumnPr";
+ public static readonly XName xmlPr = s + "xmlPr";
+ }
+
+ public static class SL
+ {
+ public static readonly XNamespace sl =
+ "http://schemas.openxmlformats.org/schemaLibrary/2006/main";
+ public static readonly XName manifestLocation = sl + "manifestLocation";
+ public static readonly XName schema = sl + "schema";
+ public static readonly XName schemaLibrary = sl + "schemaLibrary";
+ public static readonly XName uri = sl + "uri";
+ }
+
+ public static class SLE
+ {
+ public static readonly XNamespace sle =
+ "http://schemas.microsoft.com/office/drawing/2010/slicer";
+ public static readonly XName slicer = sle + "slicer";
+ }
+
+ public static class VML
+ {
+ public static readonly XNamespace vml =
+ "urn:schemas-microsoft-com:vml";
+ public static readonly XName arc = vml + "arc";
+ public static readonly XName background = vml + "background";
+ public static readonly XName curve = vml + "curve";
+ public static readonly XName ext = vml + "ext";
+ public static readonly XName f = vml + "f";
+ public static readonly XName fill = vml + "fill";
+ public static readonly XName formulas = vml + "formulas";
+ public static readonly XName group = vml + "group";
+ public static readonly XName h = vml + "h";
+ public static readonly XName handles = vml + "handles";
+ public static readonly XName image = vml + "image";
+ public static readonly XName imagedata = vml + "imagedata";
+ public static readonly XName line = vml + "line";
+ public static readonly XName oval = vml + "oval";
+ public static readonly XName path = vml + "path";
+ public static readonly XName polyline = vml + "polyline";
+ public static readonly XName rect = vml + "rect";
+ public static readonly XName roundrect = vml + "roundrect";
+ public static readonly XName shadow = vml + "shadow";
+ public static readonly XName shape = vml + "shape";
+ public static readonly XName shapetype = vml + "shapetype";
+ public static readonly XName stroke = vml + "stroke";
+ public static readonly XName textbox = vml + "textbox";
+ public static readonly XName textpath = vml + "textpath";
+ }
+
+ public static class VT
+ {
+ public static readonly XNamespace vt =
+ "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
+ public static readonly XName _bool = vt + "bool";
+ public static readonly XName filetime = vt + "filetime";
+ public static readonly XName i4 = vt + "i4";
+ public static readonly XName lpstr = vt + "lpstr";
+ public static readonly XName lpwstr = vt + "lpwstr";
+ public static readonly XName r8 = vt + "r8";
+ public static readonly XName variant = vt + "variant";
+ public static readonly XName vector = vt + "vector";
+ }
+
+ public static class W
+ {
+ public static readonly XNamespace w =
+ "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
+ public static readonly XName abstractNum = w + "abstractNum";
+ public static readonly XName abstractNumId = w + "abstractNumId";
+ public static readonly XName accent1 = w + "accent1";
+ public static readonly XName accent2 = w + "accent2";
+ public static readonly XName accent3 = w + "accent3";
+ public static readonly XName accent4 = w + "accent4";
+ public static readonly XName accent5 = w + "accent5";
+ public static readonly XName accent6 = w + "accent6";
+ public static readonly XName activeRecord = w + "activeRecord";
+ public static readonly XName activeWritingStyle = w + "activeWritingStyle";
+ public static readonly XName actualPg = w + "actualPg";
+ public static readonly XName addressFieldName = w + "addressFieldName";
+ public static readonly XName adjustLineHeightInTable = w + "adjustLineHeightInTable";
+ public static readonly XName adjustRightInd = w + "adjustRightInd";
+ public static readonly XName after = w + "after";
+ public static readonly XName afterAutospacing = w + "afterAutospacing";
+ public static readonly XName afterLines = w + "afterLines";
+ public static readonly XName algIdExt = w + "algIdExt";
+ public static readonly XName algIdExtSource = w + "algIdExtSource";
+ public static readonly XName alias = w + "alias";
+ public static readonly XName aliases = w + "aliases";
+ public static readonly XName alignBordersAndEdges = w + "alignBordersAndEdges";
+ public static readonly XName alignment = w + "alignment";
+ public static readonly XName alignTablesRowByRow = w + "alignTablesRowByRow";
+ public static readonly XName allowPNG = w + "allowPNG";
+ public static readonly XName allowSpaceOfSameStyleInTable = w + "allowSpaceOfSameStyleInTable";
+ public static readonly XName altChunk = w + "altChunk";
+ public static readonly XName altChunkPr = w + "altChunkPr";
+ public static readonly XName altName = w + "altName";
+ public static readonly XName alwaysMergeEmptyNamespace = w + "alwaysMergeEmptyNamespace";
+ public static readonly XName alwaysShowPlaceholderText = w + "alwaysShowPlaceholderText";
+ public static readonly XName anchor = w + "anchor";
+ public static readonly XName anchorLock = w + "anchorLock";
+ public static readonly XName annotationRef = w + "annotationRef";
+ public static readonly XName applyBreakingRules = w + "applyBreakingRules";
+ public static readonly XName appName = w + "appName";
+ public static readonly XName ascii = w + "ascii";
+ public static readonly XName asciiTheme = w + "asciiTheme";
+ public static readonly XName attachedSchema = w + "attachedSchema";
+ public static readonly XName attachedTemplate = w + "attachedTemplate";
+ public static readonly XName attr = w + "attr";
+ public static readonly XName author = w + "author";
+ public static readonly XName autofitToFirstFixedWidthCell = w + "autofitToFirstFixedWidthCell";
+ public static readonly XName autoFormatOverride = w + "autoFormatOverride";
+ public static readonly XName autoHyphenation = w + "autoHyphenation";
+ public static readonly XName autoRedefine = w + "autoRedefine";
+ public static readonly XName autoSpaceDE = w + "autoSpaceDE";
+ public static readonly XName autoSpaceDN = w + "autoSpaceDN";
+ public static readonly XName autoSpaceLikeWord95 = w + "autoSpaceLikeWord95";
+ public static readonly XName b = w + "b";
+ public static readonly XName background = w + "background";
+ public static readonly XName balanceSingleByteDoubleByteWidth = w + "balanceSingleByteDoubleByteWidth";
+ public static readonly XName bar = w + "bar";
+ public static readonly XName basedOn = w + "basedOn";
+ public static readonly XName bCs = w + "bCs";
+ public static readonly XName bdr = w + "bdr";
+ public static readonly XName before = w + "before";
+ public static readonly XName beforeAutospacing = w + "beforeAutospacing";
+ public static readonly XName beforeLines = w + "beforeLines";
+ public static readonly XName behavior = w + "behavior";
+ public static readonly XName behaviors = w + "behaviors";
+ public static readonly XName between = w + "between";
+ public static readonly XName bg1 = w + "bg1";
+ public static readonly XName bg2 = w + "bg2";
+ public static readonly XName bibliography = w + "bibliography";
+ public static readonly XName bidi = w + "bidi";
+ public static readonly XName bidiVisual = w + "bidiVisual";
+ public static readonly XName blockQuote = w + "blockQuote";
+ public static readonly XName body = w + "body";
+ public static readonly XName bodyDiv = w + "bodyDiv";
+ public static readonly XName bookFoldPrinting = w + "bookFoldPrinting";
+ public static readonly XName bookFoldPrintingSheets = w + "bookFoldPrintingSheets";
+ public static readonly XName bookFoldRevPrinting = w + "bookFoldRevPrinting";
+ public static readonly XName bookmarkEnd = w + "bookmarkEnd";
+ public static readonly XName bookmarkStart = w + "bookmarkStart";
+ public static readonly XName bordersDoNotSurroundFooter = w + "bordersDoNotSurroundFooter";
+ public static readonly XName bordersDoNotSurroundHeader = w + "bordersDoNotSurroundHeader";
+ public static readonly XName bottom = w + "bottom";
+ public static readonly XName bottomFromText = w + "bottomFromText";
+ public static readonly XName br = w + "br";
+ public static readonly XName cachedColBalance = w + "cachedColBalance";
+ public static readonly XName calcOnExit = w + "calcOnExit";
+ public static readonly XName calendar = w + "calendar";
+ public static readonly XName cantSplit = w + "cantSplit";
+ public static readonly XName caps = w + "caps";
+ public static readonly XName category = w + "category";
+ public static readonly XName cellDel = w + "cellDel";
+ public static readonly XName cellIns = w + "cellIns";
+ public static readonly XName cellMerge = w + "cellMerge";
+ public static readonly XName chapSep = w + "chapSep";
+ public static readonly XName chapStyle = w + "chapStyle";
+ public static readonly XName _char = w + "char";
+ public static readonly XName characterSpacingControl = w + "characterSpacingControl";
+ public static readonly XName charset = w + "charset";
+ public static readonly XName charSpace = w + "charSpace";
+ public static readonly XName checkBox = w + "checkBox";
+ public static readonly XName _checked = w + "checked";
+ public static readonly XName checkErrors = w + "checkErrors";
+ public static readonly XName checkStyle = w + "checkStyle";
+ public static readonly XName citation = w + "citation";
+ public static readonly XName clear = w + "clear";
+ public static readonly XName clickAndTypeStyle = w + "clickAndTypeStyle";
+ public static readonly XName clrSchemeMapping = w + "clrSchemeMapping";
+ public static readonly XName cnfStyle = w + "cnfStyle";
+ public static readonly XName code = w + "code";
+ public static readonly XName col = w + "col";
+ public static readonly XName colDelim = w + "colDelim";
+ public static readonly XName colFirst = w + "colFirst";
+ public static readonly XName colLast = w + "colLast";
+ public static readonly XName color = w + "color";
+ public static readonly XName cols = w + "cols";
+ public static readonly XName column = w + "column";
+ public static readonly XName combine = w + "combine";
+ public static readonly XName combineBrackets = w + "combineBrackets";
+ public static readonly XName comboBox = w + "comboBox";
+ public static readonly XName comment = w + "comment";
+ public static readonly XName commentRangeEnd = w + "commentRangeEnd";
+ public static readonly XName commentRangeStart = w + "commentRangeStart";
+ public static readonly XName commentReference = w + "commentReference";
+ public static readonly XName comments = w + "comments";
+ public static readonly XName compat = w + "compat";
+ public static readonly XName compatSetting = w + "compatSetting";
+ public static readonly XName connectString = w + "connectString";
+ public static readonly XName consecutiveHyphenLimit = w + "consecutiveHyphenLimit";
+ public static readonly XName contentPart = w + "contentPart";
+ public static readonly XName contextualSpacing = w + "contextualSpacing";
+ public static readonly XName continuationSeparator = w + "continuationSeparator";
+ public static readonly XName control = w + "control";
+ public static readonly XName convMailMergeEsc = w + "convMailMergeEsc";
+ public static readonly XName count = w + "count";
+ public static readonly XName countBy = w + "countBy";
+ public static readonly XName cr = w + "cr";
+ public static readonly XName cryptAlgorithmClass = w + "cryptAlgorithmClass";
+ public static readonly XName cryptAlgorithmSid = w + "cryptAlgorithmSid";
+ public static readonly XName cryptAlgorithmType = w + "cryptAlgorithmType";
+ public static readonly XName cryptProvider = w + "cryptProvider";
+ public static readonly XName cryptProviderType = w + "cryptProviderType";
+ public static readonly XName cryptProviderTypeExt = w + "cryptProviderTypeExt";
+ public static readonly XName cryptProviderTypeExtSource = w + "cryptProviderTypeExtSource";
+ public static readonly XName cryptSpinCount = w + "cryptSpinCount";
+ public static readonly XName cs = w + "cs";
+ public static readonly XName csb0 = w + "csb0";
+ public static readonly XName csb1 = w + "csb1";
+ public static readonly XName cstheme = w + "cstheme";
+ public static readonly XName customMarkFollows = w + "customMarkFollows";
+ public static readonly XName customStyle = w + "customStyle";
+ public static readonly XName customXml = w + "customXml";
+ public static readonly XName customXmlDelRangeEnd = w + "customXmlDelRangeEnd";
+ public static readonly XName customXmlDelRangeStart = w + "customXmlDelRangeStart";
+ public static readonly XName customXmlInsRangeEnd = w + "customXmlInsRangeEnd";
+ public static readonly XName customXmlInsRangeStart = w + "customXmlInsRangeStart";
+ public static readonly XName customXmlMoveFromRangeEnd = w + "customXmlMoveFromRangeEnd";
+ public static readonly XName customXmlMoveFromRangeStart = w + "customXmlMoveFromRangeStart";
+ public static readonly XName customXmlMoveToRangeEnd = w + "customXmlMoveToRangeEnd";
+ public static readonly XName customXmlMoveToRangeStart = w + "customXmlMoveToRangeStart";
+ public static readonly XName customXmlPr = w + "customXmlPr";
+ public static readonly XName dataBinding = w + "dataBinding";
+ public static readonly XName dataSource = w + "dataSource";
+ public static readonly XName dataType = w + "dataType";
+ public static readonly XName date = w + "date";
+ public static readonly XName dateFormat = w + "dateFormat";
+ public static readonly XName dayLong = w + "dayLong";
+ public static readonly XName dayShort = w + "dayShort";
+ public static readonly XName ddList = w + "ddList";
+ public static readonly XName decimalSymbol = w + "decimalSymbol";
+ public static readonly XName _default = w + "default";
+ public static readonly XName defaultTableStyle = w + "defaultTableStyle";
+ public static readonly XName defaultTabStop = w + "defaultTabStop";
+ public static readonly XName defLockedState = w + "defLockedState";
+ public static readonly XName defQFormat = w + "defQFormat";
+ public static readonly XName defSemiHidden = w + "defSemiHidden";
+ public static readonly XName defUIPriority = w + "defUIPriority";
+ public static readonly XName defUnhideWhenUsed = w + "defUnhideWhenUsed";
+ public static readonly XName del = w + "del";
+ public static readonly XName delInstrText = w + "delInstrText";
+ public static readonly XName delText = w + "delText";
+ public static readonly XName description = w + "description";
+ public static readonly XName destination = w + "destination";
+ public static readonly XName dir = w + "dir";
+ public static readonly XName dirty = w + "dirty";
+ public static readonly XName displacedByCustomXml = w + "displacedByCustomXml";
+ public static readonly XName display = w + "display";
+ public static readonly XName displayBackgroundShape = w + "displayBackgroundShape";
+ public static readonly XName displayHangulFixedWidth = w + "displayHangulFixedWidth";
+ public static readonly XName displayHorizontalDrawingGridEvery = w + "displayHorizontalDrawingGridEvery";
+ public static readonly XName displayText = w + "displayText";
+ public static readonly XName displayVerticalDrawingGridEvery = w + "displayVerticalDrawingGridEvery";
+ public static readonly XName distance = w + "distance";
+ public static readonly XName div = w + "div";
+ public static readonly XName divBdr = w + "divBdr";
+ public static readonly XName divId = w + "divId";
+ public static readonly XName divs = w + "divs";
+ public static readonly XName divsChild = w + "divsChild";
+ public static readonly XName dllVersion = w + "dllVersion";
+ public static readonly XName docDefaults = w + "docDefaults";
+ public static readonly XName docGrid = w + "docGrid";
+ public static readonly XName docLocation = w + "docLocation";
+ public static readonly XName docPart = w + "docPart";
+ public static readonly XName docPartBody = w + "docPartBody";
+ public static readonly XName docPartCategory = w + "docPartCategory";
+ public static readonly XName docPartGallery = w + "docPartGallery";
+ public static readonly XName docPartList = w + "docPartList";
+ public static readonly XName docPartObj = w + "docPartObj";
+ public static readonly XName docPartPr = w + "docPartPr";
+ public static readonly XName docParts = w + "docParts";
+ public static readonly XName docPartUnique = w + "docPartUnique";
+ public static readonly XName document = w + "document";
+ public static readonly XName documentProtection = w + "documentProtection";
+ public static readonly XName documentType = w + "documentType";
+ public static readonly XName docVar = w + "docVar";
+ public static readonly XName docVars = w + "docVars";
+ public static readonly XName doNotAutoCompressPictures = w + "doNotAutoCompressPictures";
+ public static readonly XName doNotAutofitConstrainedTables = w + "doNotAutofitConstrainedTables";
+ public static readonly XName doNotBreakConstrainedForcedTable = w + "doNotBreakConstrainedForcedTable";
+ public static readonly XName doNotBreakWrappedTables = w + "doNotBreakWrappedTables";
+ public static readonly XName doNotDemarcateInvalidXml = w + "doNotDemarcateInvalidXml";
+ public static readonly XName doNotDisplayPageBoundaries = w + "doNotDisplayPageBoundaries";
+ public static readonly XName doNotEmbedSmartTags = w + "doNotEmbedSmartTags";
+ public static readonly XName doNotExpandShiftReturn = w + "doNotExpandShiftReturn";
+ public static readonly XName doNotHyphenateCaps = w + "doNotHyphenateCaps";
+ public static readonly XName doNotIncludeSubdocsInStats = w + "doNotIncludeSubdocsInStats";
+ public static readonly XName doNotLeaveBackslashAlone = w + "doNotLeaveBackslashAlone";
+ public static readonly XName doNotOrganizeInFolder = w + "doNotOrganizeInFolder";
+ public static readonly XName doNotRelyOnCSS = w + "doNotRelyOnCSS";
+ public static readonly XName doNotSaveAsSingleFile = w + "doNotSaveAsSingleFile";
+ public static readonly XName doNotShadeFormData = w + "doNotShadeFormData";
+ public static readonly XName doNotSnapToGridInCell = w + "doNotSnapToGridInCell";
+ public static readonly XName doNotSuppressBlankLines = w + "doNotSuppressBlankLines";
+ public static readonly XName doNotSuppressIndentation = w + "doNotSuppressIndentation";
+ public static readonly XName doNotSuppressParagraphBorders = w + "doNotSuppressParagraphBorders";
+ public static readonly XName doNotTrackFormatting = w + "doNotTrackFormatting";
+ public static readonly XName doNotTrackMoves = w + "doNotTrackMoves";
+ public static readonly XName doNotUseEastAsianBreakRules = w + "doNotUseEastAsianBreakRules";
+ public static readonly XName doNotUseHTMLParagraphAutoSpacing = w + "doNotUseHTMLParagraphAutoSpacing";
+ public static readonly XName doNotUseIndentAsNumberingTabStop = w + "doNotUseIndentAsNumberingTabStop";
+ public static readonly XName doNotUseLongFileNames = w + "doNotUseLongFileNames";
+ public static readonly XName doNotUseMarginsForDrawingGridOrigin = w + "doNotUseMarginsForDrawingGridOrigin";
+ public static readonly XName doNotValidateAgainstSchema = w + "doNotValidateAgainstSchema";
+ public static readonly XName doNotVertAlignCellWithSp = w + "doNotVertAlignCellWithSp";
+ public static readonly XName doNotVertAlignInTxbx = w + "doNotVertAlignInTxbx";
+ public static readonly XName doNotWrapTextWithPunct = w + "doNotWrapTextWithPunct";
+ public static readonly XName drawing = w + "drawing";
+ public static readonly XName drawingGridHorizontalOrigin = w + "drawingGridHorizontalOrigin";
+ public static readonly XName drawingGridHorizontalSpacing = w + "drawingGridHorizontalSpacing";
+ public static readonly XName drawingGridVerticalOrigin = w + "drawingGridVerticalOrigin";
+ public static readonly XName drawingGridVerticalSpacing = w + "drawingGridVerticalSpacing";
+ public static readonly XName dropCap = w + "dropCap";
+ public static readonly XName dropDownList = w + "dropDownList";
+ public static readonly XName dstrike = w + "dstrike";
+ public static readonly XName dxaOrig = w + "dxaOrig";
+ public static readonly XName dyaOrig = w + "dyaOrig";
+ public static readonly XName dynamicAddress = w + "dynamicAddress";
+ public static readonly XName eastAsia = w + "eastAsia";
+ public static readonly XName eastAsianLayout = w + "eastAsianLayout";
+ public static readonly XName eastAsiaTheme = w + "eastAsiaTheme";
+ public static readonly XName ed = w + "ed";
+ public static readonly XName edGrp = w + "edGrp";
+ public static readonly XName edit = w + "edit";
+ public static readonly XName effect = w + "effect";
+ public static readonly XName element = w + "element";
+ public static readonly XName em = w + "em";
+ public static readonly XName embedBold = w + "embedBold";
+ public static readonly XName embedBoldItalic = w + "embedBoldItalic";
+ public static readonly XName embedItalic = w + "embedItalic";
+ public static readonly XName embedRegular = w + "embedRegular";
+ public static readonly XName embedSystemFonts = w + "embedSystemFonts";
+ public static readonly XName embedTrueTypeFonts = w + "embedTrueTypeFonts";
+ public static readonly XName emboss = w + "emboss";
+ public static readonly XName enabled = w + "enabled";
+ public static readonly XName encoding = w + "encoding";
+ public static readonly XName endnote = w + "endnote";
+ public static readonly XName endnotePr = w + "endnotePr";
+ public static readonly XName endnoteRef = w + "endnoteRef";
+ public static readonly XName endnoteReference = w + "endnoteReference";
+ public static readonly XName endnotes = w + "endnotes";
+ public static readonly XName enforcement = w + "enforcement";
+ public static readonly XName entryMacro = w + "entryMacro";
+ public static readonly XName equalWidth = w + "equalWidth";
+ public static readonly XName equation = w + "equation";
+ public static readonly XName evenAndOddHeaders = w + "evenAndOddHeaders";
+ public static readonly XName exitMacro = w + "exitMacro";
+ public static readonly XName family = w + "family";
+ public static readonly XName ffData = w + "ffData";
+ public static readonly XName fHdr = w + "fHdr";
+ public static readonly XName fieldMapData = w + "fieldMapData";
+ public static readonly XName fill = w + "fill";
+ public static readonly XName first = w + "first";
+ public static readonly XName firstColumn = w + "firstColumn";
+ public static readonly XName firstLine = w + "firstLine";
+ public static readonly XName firstLineChars = w + "firstLineChars";
+ public static readonly XName firstRow = w + "firstRow";
+ public static readonly XName fitText = w + "fitText";
+ public static readonly XName flatBorders = w + "flatBorders";
+ public static readonly XName fldChar = w + "fldChar";
+ public static readonly XName fldCharType = w + "fldCharType";
+ public static readonly XName fldData = w + "fldData";
+ public static readonly XName fldLock = w + "fldLock";
+ public static readonly XName fldSimple = w + "fldSimple";
+ public static readonly XName fmt = w + "fmt";
+ public static readonly XName followedHyperlink = w + "followedHyperlink";
+ public static readonly XName font = w + "font";
+ public static readonly XName fontKey = w + "fontKey";
+ public static readonly XName fonts = w + "fonts";
+ public static readonly XName fontSz = w + "fontSz";
+ public static readonly XName footer = w + "footer";
+ public static readonly XName footerReference = w + "footerReference";
+ public static readonly XName footnote = w + "footnote";
+ public static readonly XName footnoteLayoutLikeWW8 = w + "footnoteLayoutLikeWW8";
+ public static readonly XName footnotePr = w + "footnotePr";
+ public static readonly XName footnoteRef = w + "footnoteRef";
+ public static readonly XName footnoteReference = w + "footnoteReference";
+ public static readonly XName footnotes = w + "footnotes";
+ public static readonly XName forceUpgrade = w + "forceUpgrade";
+ public static readonly XName forgetLastTabAlignment = w + "forgetLastTabAlignment";
+ public static readonly XName format = w + "format";
+ public static readonly XName formatting = w + "formatting";
+ public static readonly XName formProt = w + "formProt";
+ public static readonly XName formsDesign = w + "formsDesign";
+ public static readonly XName frame = w + "frame";
+ public static readonly XName frameLayout = w + "frameLayout";
+ public static readonly XName framePr = w + "framePr";
+ public static readonly XName frameset = w + "frameset";
+ public static readonly XName framesetSplitbar = w + "framesetSplitbar";
+ public static readonly XName ftr = w + "ftr";
+ public static readonly XName fullDate = w + "fullDate";
+ public static readonly XName gallery = w + "gallery";
+ public static readonly XName glossaryDocument = w + "glossaryDocument";
+ public static readonly XName grammar = w + "grammar";
+ public static readonly XName gridAfter = w + "gridAfter";
+ public static readonly XName gridBefore = w + "gridBefore";
+ public static readonly XName gridCol = w + "gridCol";
+ public static readonly XName gridSpan = w + "gridSpan";
+ public static readonly XName group = w + "group";
+ public static readonly XName growAutofit = w + "growAutofit";
+ public static readonly XName guid = w + "guid";
+ public static readonly XName gutter = w + "gutter";
+ public static readonly XName gutterAtTop = w + "gutterAtTop";
+ public static readonly XName h = w + "h";
+ public static readonly XName hAnchor = w + "hAnchor";
+ public static readonly XName hanging = w + "hanging";
+ public static readonly XName hangingChars = w + "hangingChars";
+ public static readonly XName hAnsi = w + "hAnsi";
+ public static readonly XName hAnsiTheme = w + "hAnsiTheme";
+ public static readonly XName hash = w + "hash";
+ public static readonly XName hdr = w + "hdr";
+ public static readonly XName hdrShapeDefaults = w + "hdrShapeDefaults";
+ public static readonly XName header = w + "header";
+ public static readonly XName headerReference = w + "headerReference";
+ public static readonly XName headerSource = w + "headerSource";
+ public static readonly XName helpText = w + "helpText";
+ public static readonly XName hidden = w + "hidden";
+ public static readonly XName hideGrammaticalErrors = w + "hideGrammaticalErrors";
+ public static readonly XName hideMark = w + "hideMark";
+ public static readonly XName hideSpellingErrors = w + "hideSpellingErrors";
+ public static readonly XName highlight = w + "highlight";
+ public static readonly XName hint = w + "hint";
+ public static readonly XName history = w + "history";
+ public static readonly XName hMerge = w + "hMerge";
+ public static readonly XName horzAnchor = w + "horzAnchor";
+ public static readonly XName hps = w + "hps";
+ public static readonly XName hpsBaseText = w + "hpsBaseText";
+ public static readonly XName hpsRaise = w + "hpsRaise";
+ public static readonly XName hRule = w + "hRule";
+ public static readonly XName hSpace = w + "hSpace";
+ public static readonly XName hyperlink = w + "hyperlink";
+ public static readonly XName hyphenationZone = w + "hyphenationZone";
+ public static readonly XName i = w + "i";
+ public static readonly XName iCs = w + "iCs";
+ public static readonly XName id = w + "id";
+ public static readonly XName ignoreMixedContent = w + "ignoreMixedContent";
+ public static readonly XName ilvl = w + "ilvl";
+ public static readonly XName imprint = w + "imprint";
+ public static readonly XName ind = w + "ind";
+ public static readonly XName initials = w + "initials";
+ public static readonly XName inkAnnotations = w + "inkAnnotations";
+ public static readonly XName ins = w + "ins";
+ public static readonly XName insDel = w + "insDel";
+ public static readonly XName insideH = w + "insideH";
+ public static readonly XName insideV = w + "insideV";
+ public static readonly XName instr = w + "instr";
+ public static readonly XName instrText = w + "instrText";
+ public static readonly XName isLgl = w + "isLgl";
+ public static readonly XName jc = w + "jc";
+ public static readonly XName keepLines = w + "keepLines";
+ public static readonly XName keepNext = w + "keepNext";
+ public static readonly XName kern = w + "kern";
+ public static readonly XName kinsoku = w + "kinsoku";
+ public static readonly XName lang = w + "lang";
+ public static readonly XName lastColumn = w + "lastColumn";
+ public static readonly XName lastRenderedPageBreak = w + "lastRenderedPageBreak";
+ public static readonly XName lastValue = w + "lastValue";
+ public static readonly XName lastRow = w + "lastRow";
+ public static readonly XName latentStyles = w + "latentStyles";
+ public static readonly XName layoutRawTableWidth = w + "layoutRawTableWidth";
+ public static readonly XName layoutTableRowsApart = w + "layoutTableRowsApart";
+ public static readonly XName leader = w + "leader";
+ public static readonly XName left = w + "left";
+ public static readonly XName leftChars = w + "leftChars";
+ public static readonly XName leftFromText = w + "leftFromText";
+ public static readonly XName legacy = w + "legacy";
+ public static readonly XName legacyIndent = w + "legacyIndent";
+ public static readonly XName legacySpace = w + "legacySpace";
+ public static readonly XName lid = w + "lid";
+ public static readonly XName line = w + "line";
+ public static readonly XName linePitch = w + "linePitch";
+ public static readonly XName lineRule = w + "lineRule";
+ public static readonly XName lines = w + "lines";
+ public static readonly XName lineWrapLikeWord6 = w + "lineWrapLikeWord6";
+ public static readonly XName link = w + "link";
+ public static readonly XName linkedToFile = w + "linkedToFile";
+ public static readonly XName linkStyles = w + "linkStyles";
+ public static readonly XName linkToQuery = w + "linkToQuery";
+ public static readonly XName listEntry = w + "listEntry";
+ public static readonly XName listItem = w + "listItem";
+ public static readonly XName listSeparator = w + "listSeparator";
+ public static readonly XName lnNumType = w + "lnNumType";
+ public static readonly XName _lock = w + "lock";
+ public static readonly XName locked = w + "locked";
+ public static readonly XName lsdException = w + "lsdException";
+ public static readonly XName lvl = w + "lvl";
+ public static readonly XName lvlJc = w + "lvlJc";
+ public static readonly XName lvlOverride = w + "lvlOverride";
+ public static readonly XName lvlPicBulletId = w + "lvlPicBulletId";
+ public static readonly XName lvlRestart = w + "lvlRestart";
+ public static readonly XName lvlText = w + "lvlText";
+ public static readonly XName mailAsAttachment = w + "mailAsAttachment";
+ public static readonly XName mailMerge = w + "mailMerge";
+ public static readonly XName mailSubject = w + "mailSubject";
+ public static readonly XName mainDocumentType = w + "mainDocumentType";
+ public static readonly XName mappedName = w + "mappedName";
+ public static readonly XName marBottom = w + "marBottom";
+ public static readonly XName marH = w + "marH";
+ public static readonly XName markup = w + "markup";
+ public static readonly XName marLeft = w + "marLeft";
+ public static readonly XName marRight = w + "marRight";
+ public static readonly XName marTop = w + "marTop";
+ public static readonly XName marW = w + "marW";
+ public static readonly XName matchSrc = w + "matchSrc";
+ public static readonly XName maxLength = w + "maxLength";
+ public static readonly XName mirrorIndents = w + "mirrorIndents";
+ public static readonly XName mirrorMargins = w + "mirrorMargins";
+ public static readonly XName monthLong = w + "monthLong";
+ public static readonly XName monthShort = w + "monthShort";
+ public static readonly XName moveFrom = w + "moveFrom";
+ public static readonly XName moveFromRangeEnd = w + "moveFromRangeEnd";
+ public static readonly XName moveFromRangeStart = w + "moveFromRangeStart";
+ public static readonly XName moveTo = w + "moveTo";
+ public static readonly XName moveToRangeEnd = w + "moveToRangeEnd";
+ public static readonly XName moveToRangeStart = w + "moveToRangeStart";
+ public static readonly XName multiLevelType = w + "multiLevelType";
+ public static readonly XName multiLine = w + "multiLine";
+ public static readonly XName mwSmallCaps = w + "mwSmallCaps";
+ public static readonly XName name = w + "name";
+ public static readonly XName namespaceuri = w + "namespaceuri";
+ public static readonly XName next = w + "next";
+ public static readonly XName nlCheck = w + "nlCheck";
+ public static readonly XName noBorder = w + "noBorder";
+ public static readonly XName noBreakHyphen = w + "noBreakHyphen";
+ public static readonly XName noColumnBalance = w + "noColumnBalance";
+ public static readonly XName noEndnote = w + "noEndnote";
+ public static readonly XName noExtraLineSpacing = w + "noExtraLineSpacing";
+ public static readonly XName noHBand = w + "noHBand";
+ public static readonly XName noLeading = w + "noLeading";
+ public static readonly XName noLineBreaksAfter = w + "noLineBreaksAfter";
+ public static readonly XName noLineBreaksBefore = w + "noLineBreaksBefore";
+ public static readonly XName noProof = w + "noProof";
+ public static readonly XName noPunctuationKerning = w + "noPunctuationKerning";
+ public static readonly XName noResizeAllowed = w + "noResizeAllowed";
+ public static readonly XName noSpaceRaiseLower = w + "noSpaceRaiseLower";
+ public static readonly XName noTabHangInd = w + "noTabHangInd";
+ public static readonly XName notTrueType = w + "notTrueType";
+ public static readonly XName noVBand = w + "noVBand";
+ public static readonly XName noWrap = w + "noWrap";
+ public static readonly XName nsid = w + "nsid";
+ public static readonly XName _null = w + "null";
+ public static readonly XName num = w + "num";
+ public static readonly XName numbering = w + "numbering";
+ public static readonly XName numberingChange = w + "numberingChange";
+ public static readonly XName numFmt = w + "numFmt";
+ public static readonly XName numId = w + "numId";
+ public static readonly XName numIdMacAtCleanup = w + "numIdMacAtCleanup";
+ public static readonly XName numPicBullet = w + "numPicBullet";
+ public static readonly XName numPicBulletId = w + "numPicBulletId";
+ public static readonly XName numPr = w + "numPr";
+ public static readonly XName numRestart = w + "numRestart";
+ public static readonly XName numStart = w + "numStart";
+ public static readonly XName numStyleLink = w + "numStyleLink";
+ public static readonly XName _object = w + "object";
+ public static readonly XName odso = w + "odso";
+ public static readonly XName offsetFrom = w + "offsetFrom";
+ public static readonly XName oMath = w + "oMath";
+ public static readonly XName optimizeForBrowser = w + "optimizeForBrowser";
+ public static readonly XName orient = w + "orient";
+ public static readonly XName original = w + "original";
+ public static readonly XName other = w + "other";
+ public static readonly XName outline = w + "outline";
+ public static readonly XName outlineLvl = w + "outlineLvl";
+ public static readonly XName overflowPunct = w + "overflowPunct";
+ public static readonly XName p = w + "p";
+ public static readonly XName pageBreakBefore = w + "pageBreakBefore";
+ public static readonly XName panose1 = w + "panose1";
+ public static readonly XName paperSrc = w + "paperSrc";
+ public static readonly XName pBdr = w + "pBdr";
+ public static readonly XName percent = w + "percent";
+ public static readonly XName permEnd = w + "permEnd";
+ public static readonly XName permStart = w + "permStart";
+ public static readonly XName personal = w + "personal";
+ public static readonly XName personalCompose = w + "personalCompose";
+ public static readonly XName personalReply = w + "personalReply";
+ public static readonly XName pgBorders = w + "pgBorders";
+ public static readonly XName pgMar = w + "pgMar";
+ public static readonly XName pgNum = w + "pgNum";
+ public static readonly XName pgNumType = w + "pgNumType";
+ public static readonly XName pgSz = w + "pgSz";
+ public static readonly XName pict = w + "pict";
+ public static readonly XName picture = w + "picture";
+ public static readonly XName pitch = w + "pitch";
+ public static readonly XName pixelsPerInch = w + "pixelsPerInch";
+ public static readonly XName placeholder = w + "placeholder";
+ public static readonly XName pos = w + "pos";
+ public static readonly XName position = w + "position";
+ public static readonly XName pPr = w + "pPr";
+ public static readonly XName pPrChange = w + "pPrChange";
+ public static readonly XName pPrDefault = w + "pPrDefault";
+ public static readonly XName prefixMappings = w + "prefixMappings";
+ public static readonly XName printBodyTextBeforeHeader = w + "printBodyTextBeforeHeader";
+ public static readonly XName printColBlack = w + "printColBlack";
+ public static readonly XName printerSettings = w + "printerSettings";
+ public static readonly XName printFormsData = w + "printFormsData";
+ public static readonly XName printFractionalCharacterWidth = w + "printFractionalCharacterWidth";
+ public static readonly XName printPostScriptOverText = w + "printPostScriptOverText";
+ public static readonly XName printTwoOnOne = w + "printTwoOnOne";
+ public static readonly XName proofErr = w + "proofErr";
+ public static readonly XName proofState = w + "proofState";
+ public static readonly XName pStyle = w + "pStyle";
+ public static readonly XName ptab = w + "ptab";
+ public static readonly XName qFormat = w + "qFormat";
+ public static readonly XName query = w + "query";
+ public static readonly XName r = w + "r";
+ public static readonly XName readModeInkLockDown = w + "readModeInkLockDown";
+ public static readonly XName recipientData = w + "recipientData";
+ public static readonly XName recommended = w + "recommended";
+ public static readonly XName relativeTo = w + "relativeTo";
+ public static readonly XName relyOnVML = w + "relyOnVML";
+ public static readonly XName removeDateAndTime = w + "removeDateAndTime";
+ public static readonly XName removePersonalInformation = w + "removePersonalInformation";
+ public static readonly XName restart = w + "restart";
+ public static readonly XName result = w + "result";
+ public static readonly XName revisionView = w + "revisionView";
+ public static readonly XName rFonts = w + "rFonts";
+ public static readonly XName richText = w + "richText";
+ public static readonly XName right = w + "right";
+ public static readonly XName rightChars = w + "rightChars";
+ public static readonly XName rightFromText = w + "rightFromText";
+ public static readonly XName rPr = w + "rPr";
+ public static readonly XName rPrChange = w + "rPrChange";
+ public static readonly XName rPrDefault = w + "rPrDefault";
+ public static readonly XName rsid = w + "rsid";
+ public static readonly XName rsidDel = w + "rsidDel";
+ public static readonly XName rsidP = w + "rsidP";
+ public static readonly XName rsidR = w + "rsidR";
+ public static readonly XName rsidRDefault = w + "rsidRDefault";
+ public static readonly XName rsidRoot = w + "rsidRoot";
+ public static readonly XName rsidRPr = w + "rsidRPr";
+ public static readonly XName rsids = w + "rsids";
+ public static readonly XName rsidSect = w + "rsidSect";
+ public static readonly XName rsidTr = w + "rsidTr";
+ public static readonly XName rStyle = w + "rStyle";
+ public static readonly XName rt = w + "rt";
+ public static readonly XName rtl = w + "rtl";
+ public static readonly XName rtlGutter = w + "rtlGutter";
+ public static readonly XName ruby = w + "ruby";
+ public static readonly XName rubyAlign = w + "rubyAlign";
+ public static readonly XName rubyBase = w + "rubyBase";
+ public static readonly XName rubyPr = w + "rubyPr";
+ public static readonly XName salt = w + "salt";
+ public static readonly XName saveFormsData = w + "saveFormsData";
+ public static readonly XName saveInvalidXml = w + "saveInvalidXml";
+ public static readonly XName savePreviewPicture = w + "savePreviewPicture";
+ public static readonly XName saveSmartTagsAsXml = w + "saveSmartTagsAsXml";
+ public static readonly XName saveSubsetFonts = w + "saveSubsetFonts";
+ public static readonly XName saveThroughXslt = w + "saveThroughXslt";
+ public static readonly XName saveXmlDataOnly = w + "saveXmlDataOnly";
+ public static readonly XName scrollbar = w + "scrollbar";
+ public static readonly XName sdt = w + "sdt";
+ public static readonly XName sdtContent = w + "sdtContent";
+ public static readonly XName sdtEndPr = w + "sdtEndPr";
+ public static readonly XName sdtPr = w + "sdtPr";
+ public static readonly XName sectPr = w + "sectPr";
+ public static readonly XName sectPrChange = w + "sectPrChange";
+ public static readonly XName selectFldWithFirstOrLastChar = w + "selectFldWithFirstOrLastChar";
+ public static readonly XName semiHidden = w + "semiHidden";
+ public static readonly XName sep = w + "sep";
+ public static readonly XName separator = w + "separator";
+ public static readonly XName settings = w + "settings";
+ public static readonly XName shadow = w + "shadow";
+ public static readonly XName shapeDefaults = w + "shapeDefaults";
+ public static readonly XName shapeid = w + "shapeid";
+ public static readonly XName shapeLayoutLikeWW8 = w + "shapeLayoutLikeWW8";
+ public static readonly XName shd = w + "shd";
+ public static readonly XName showBreaksInFrames = w + "showBreaksInFrames";
+ public static readonly XName showEnvelope = w + "showEnvelope";
+ public static readonly XName showingPlcHdr = w + "showingPlcHdr";
+ public static readonly XName showXMLTags = w + "showXMLTags";
+ public static readonly XName sig = w + "sig";
+ public static readonly XName size = w + "size";
+ public static readonly XName sizeAuto = w + "sizeAuto";
+ public static readonly XName smallCaps = w + "smallCaps";
+ public static readonly XName smartTag = w + "smartTag";
+ public static readonly XName smartTagPr = w + "smartTagPr";
+ public static readonly XName smartTagType = w + "smartTagType";
+ public static readonly XName snapToGrid = w + "snapToGrid";
+ public static readonly XName softHyphen = w + "softHyphen";
+ public static readonly XName solutionID = w + "solutionID";
+ public static readonly XName sourceFileName = w + "sourceFileName";
+ public static readonly XName space = w + "space";
+ public static readonly XName spaceForUL = w + "spaceForUL";
+ public static readonly XName spacing = w + "spacing";
+ public static readonly XName spacingInWholePoints = w + "spacingInWholePoints";
+ public static readonly XName specVanish = w + "specVanish";
+ public static readonly XName spelling = w + "spelling";
+ public static readonly XName splitPgBreakAndParaMark = w + "splitPgBreakAndParaMark";
+ public static readonly XName src = w + "src";
+ public static readonly XName start = w + "start";
+ public static readonly XName startOverride = w + "startOverride";
+ public static readonly XName statusText = w + "statusText";
+ public static readonly XName storeItemID = w + "storeItemID";
+ public static readonly XName storeMappedDataAs = w + "storeMappedDataAs";
+ public static readonly XName strictFirstAndLastChars = w + "strictFirstAndLastChars";
+ public static readonly XName strike = w + "strike";
+ public static readonly XName style = w + "style";
+ public static readonly XName styleId = w + "styleId";
+ public static readonly XName styleLink = w + "styleLink";
+ public static readonly XName styleLockQFSet = w + "styleLockQFSet";
+ public static readonly XName styleLockTheme = w + "styleLockTheme";
+ public static readonly XName stylePaneFormatFilter = w + "stylePaneFormatFilter";
+ public static readonly XName stylePaneSortMethod = w + "stylePaneSortMethod";
+ public static readonly XName styles = w + "styles";
+ public static readonly XName subDoc = w + "subDoc";
+ public static readonly XName subFontBySize = w + "subFontBySize";
+ public static readonly XName subsetted = w + "subsetted";
+ public static readonly XName suff = w + "suff";
+ public static readonly XName summaryLength = w + "summaryLength";
+ public static readonly XName suppressAutoHyphens = w + "suppressAutoHyphens";
+ public static readonly XName suppressBottomSpacing = w + "suppressBottomSpacing";
+ public static readonly XName suppressLineNumbers = w + "suppressLineNumbers";
+ public static readonly XName suppressOverlap = w + "suppressOverlap";
+ public static readonly XName suppressSpacingAtTopOfPage = w + "suppressSpacingAtTopOfPage";
+ public static readonly XName suppressSpBfAfterPgBrk = w + "suppressSpBfAfterPgBrk";
+ public static readonly XName suppressTopSpacing = w + "suppressTopSpacing";
+ public static readonly XName suppressTopSpacingWP = w + "suppressTopSpacingWP";
+ public static readonly XName swapBordersFacingPages = w + "swapBordersFacingPages";
+ public static readonly XName sym = w + "sym";
+ public static readonly XName sz = w + "sz";
+ public static readonly XName szCs = w + "szCs";
+ public static readonly XName t = w + "t";
+ public static readonly XName t1 = w + "t1";
+ public static readonly XName t2 = w + "t2";
+ public static readonly XName tab = w + "tab";
+ public static readonly XName table = w + "table";
+ public static readonly XName tabs = w + "tabs";
+ public static readonly XName tag = w + "tag";
+ public static readonly XName targetScreenSz = w + "targetScreenSz";
+ public static readonly XName tbl = w + "tbl";
+ public static readonly XName tblBorders = w + "tblBorders";
+ public static readonly XName tblCellMar = w + "tblCellMar";
+ public static readonly XName tblCellSpacing = w + "tblCellSpacing";
+ public static readonly XName tblGrid = w + "tblGrid";
+ public static readonly XName tblGridChange = w + "tblGridChange";
+ public static readonly XName tblHeader = w + "tblHeader";
+ public static readonly XName tblInd = w + "tblInd";
+ public static readonly XName tblLayout = w + "tblLayout";
+ public static readonly XName tblLook = w + "tblLook";
+ public static readonly XName tblOverlap = w + "tblOverlap";
+ public static readonly XName tblpPr = w + "tblpPr";
+ public static readonly XName tblPr = w + "tblPr";
+ public static readonly XName tblPrChange = w + "tblPrChange";
+ public static readonly XName tblPrEx = w + "tblPrEx";
+ public static readonly XName tblPrExChange = w + "tblPrExChange";
+ public static readonly XName tblpX = w + "tblpX";
+ public static readonly XName tblpXSpec = w + "tblpXSpec";
+ public static readonly XName tblpY = w + "tblpY";
+ public static readonly XName tblpYSpec = w + "tblpYSpec";
+ public static readonly XName tblStyle = w + "tblStyle";
+ public static readonly XName tblStyleColBandSize = w + "tblStyleColBandSize";
+ public static readonly XName tblStylePr = w + "tblStylePr";
+ public static readonly XName tblStyleRowBandSize = w + "tblStyleRowBandSize";
+ public static readonly XName tblW = w + "tblW";
+ public static readonly XName tc = w + "tc";
+ public static readonly XName tcBorders = w + "tcBorders";
+ public static readonly XName tcFitText = w + "tcFitText";
+ public static readonly XName tcMar = w + "tcMar";
+ public static readonly XName tcPr = w + "tcPr";
+ public static readonly XName tcPrChange = w + "tcPrChange";
+ public static readonly XName tcW = w + "tcW";
+ public static readonly XName temporary = w + "temporary";
+ public static readonly XName tentative = w + "tentative";
+ public static readonly XName text = w + "text";
+ public static readonly XName textAlignment = w + "textAlignment";
+ public static readonly XName textboxTightWrap = w + "textboxTightWrap";
+ public static readonly XName textDirection = w + "textDirection";
+ public static readonly XName textInput = w + "textInput";
+ public static readonly XName tgtFrame = w + "tgtFrame";
+ public static readonly XName themeColor = w + "themeColor";
+ public static readonly XName themeFill = w + "themeFill";
+ public static readonly XName themeFillShade = w + "themeFillShade";
+ public static readonly XName themeFillTint = w + "themeFillTint";
+ public static readonly XName themeFontLang = w + "themeFontLang";
+ public static readonly XName themeShade = w + "themeShade";
+ public static readonly XName themeTint = w + "themeTint";
+ public static readonly XName titlePg = w + "titlePg";
+ public static readonly XName tl2br = w + "tl2br";
+ public static readonly XName tmpl = w + "tmpl";
+ public static readonly XName tooltip = w + "tooltip";
+ public static readonly XName top = w + "top";
+ public static readonly XName topFromText = w + "topFromText";
+ public static readonly XName topLinePunct = w + "topLinePunct";
+ public static readonly XName tplc = w + "tplc";
+ public static readonly XName tr = w + "tr";
+ public static readonly XName tr2bl = w + "tr2bl";
+ public static readonly XName trackRevisions = w + "trackRevisions";
+ public static readonly XName trHeight = w + "trHeight";
+ public static readonly XName trPr = w + "trPr";
+ public static readonly XName trPrChange = w + "trPrChange";
+ public static readonly XName truncateFontHeightsLikeWP6 = w + "truncateFontHeightsLikeWP6";
+ public static readonly XName txbxContent = w + "txbxContent";
+ public static readonly XName type = w + "type";
+ public static readonly XName types = w + "types";
+ public static readonly XName u = w + "u";
+ public static readonly XName udl = w + "udl";
+ public static readonly XName uiCompat97To2003 = w + "uiCompat97To2003";
+ public static readonly XName uiPriority = w + "uiPriority";
+ public static readonly XName ulTrailSpace = w + "ulTrailSpace";
+ public static readonly XName underlineTabInNumList = w + "underlineTabInNumList";
+ public static readonly XName unhideWhenUsed = w + "unhideWhenUsed";
+ public static readonly XName updateFields = w + "updateFields";
+ public static readonly XName uri = w + "uri";
+ public static readonly XName url = w + "url";
+ public static readonly XName usb0 = w + "usb0";
+ public static readonly XName usb1 = w + "usb1";
+ public static readonly XName usb2 = w + "usb2";
+ public static readonly XName usb3 = w + "usb3";
+ public static readonly XName useAltKinsokuLineBreakRules = w + "useAltKinsokuLineBreakRules";
+ public static readonly XName useAnsiKerningPairs = w + "useAnsiKerningPairs";
+ public static readonly XName useFELayout = w + "useFELayout";
+ public static readonly XName useNormalStyleForList = w + "useNormalStyleForList";
+ public static readonly XName usePrinterMetrics = w + "usePrinterMetrics";
+ public static readonly XName useSingleBorderforContiguousCells = w + "useSingleBorderforContiguousCells";
+ public static readonly XName useWord2002TableStyleRules = w + "useWord2002TableStyleRules";
+ public static readonly XName useWord97LineBreakRules = w + "useWord97LineBreakRules";
+ public static readonly XName useXSLTWhenSaving = w + "useXSLTWhenSaving";
+ public static readonly XName val = w + "val";
+ public static readonly XName vAlign = w + "vAlign";
+ public static readonly XName value = w + "value";
+ public static readonly XName vAnchor = w + "vAnchor";
+ public static readonly XName vanish = w + "vanish";
+ public static readonly XName vendorID = w + "vendorID";
+ public static readonly XName vert = w + "vert";
+ public static readonly XName vertAlign = w + "vertAlign";
+ public static readonly XName vertAnchor = w + "vertAnchor";
+ public static readonly XName vertCompress = w + "vertCompress";
+ public static readonly XName view = w + "view";
+ public static readonly XName viewMergedData = w + "viewMergedData";
+ public static readonly XName vMerge = w + "vMerge";
+ public static readonly XName vMergeOrig = w + "vMergeOrig";
+ public static readonly XName vSpace = w + "vSpace";
+ public static readonly XName _w = w + "w";
+ public static readonly XName wAfter = w + "wAfter";
+ public static readonly XName wBefore = w + "wBefore";
+ public static readonly XName webHidden = w + "webHidden";
+ public static readonly XName webSettings = w + "webSettings";
+ public static readonly XName widowControl = w + "widowControl";
+ public static readonly XName wordWrap = w + "wordWrap";
+ public static readonly XName wpJustification = w + "wpJustification";
+ public static readonly XName wpSpaceWidth = w + "wpSpaceWidth";
+ public static readonly XName wrap = w + "wrap";
+ public static readonly XName wrapTrailSpaces = w + "wrapTrailSpaces";
+ public static readonly XName writeProtection = w + "writeProtection";
+ public static readonly XName x = w + "x";
+ public static readonly XName xAlign = w + "xAlign";
+ public static readonly XName xpath = w + "xpath";
+ public static readonly XName y = w + "y";
+ public static readonly XName yAlign = w + "yAlign";
+ public static readonly XName yearLong = w + "yearLong";
+ public static readonly XName yearShort = w + "yearShort";
+ public static readonly XName zoom = w + "zoom";
+ public static readonly XName zOrder = w + "zOrder";
+ public static readonly XName tblCaption = w + "tblCaption";
+ public static readonly XName tblDescription = w + "tblDescription";
+ public static readonly XName startChars = w + "startChars";
+ public static readonly XName end = w + "end";
+ public static readonly XName endChars = w + "endChars";
+ public static readonly XName evenHBand = w + "evenHBand";
+ public static readonly XName evenVBand = w + "evenVBand";
+ public static readonly XName firstRowFirstColumn = w + "firstRowFirstColumn";
+ public static readonly XName firstRowLastColumn = w + "firstRowLastColumn";
+ public static readonly XName lastRowFirstColumn = w + "lastRowFirstColumn";
+ public static readonly XName lastRowLastColumn = w + "lastRowLastColumn";
+ public static readonly XName oddHBand = w + "oddHBand";
+ public static readonly XName oddVBand = w + "oddVBand";
+ public static readonly XName headers = w + "headers";
+
+ public static readonly XName[] BlockLevelContentContainers =
+ {
+ W.body,
+ W.tc,
+ W.txbxContent,
+ W.hdr,
+ W.ftr,
+ W.endnote,
+ W.footnote
+ };
+
+ public static readonly XName[] SubRunLevelContent =
+ {
+ W.br,
+ W.cr,
+ W.dayLong,
+ W.dayShort,
+ W.drawing,
+ W.drawing,
+ W.monthLong,
+ W.monthShort,
+ W.noBreakHyphen,
+ W.ptab,
+ W.pgNum,
+ W.pict,
+ W.softHyphen,
+ W.sym,
+ W.t,
+ W.tab,
+ W.yearLong,
+ W.yearShort,
+ MC.AlternateContent,
+ };
+ }
+
+ public static class W10
+ {
+ public static readonly XNamespace w10 =
+ "urn:schemas-microsoft-com:office:word";
+ public static readonly XName anchorlock = w10 + "anchorlock";
+ public static readonly XName borderbottom = w10 + "borderbottom";
+ public static readonly XName borderleft = w10 + "borderleft";
+ public static readonly XName borderright = w10 + "borderright";
+ public static readonly XName bordertop = w10 + "bordertop";
+ public static readonly XName wrap = w10 + "wrap";
+ }
+
+ public static class W14
+ {
+ public static readonly XNamespace w14 =
+ "http://schemas.microsoft.com/office/word/2010/wordml";
+ public static readonly XName algn = w14 + "algn";
+ public static readonly XName alpha = w14 + "alpha";
+ public static readonly XName ang = w14 + "ang";
+ public static readonly XName b = w14 + "b";
+ public static readonly XName bevel = w14 + "bevel";
+ public static readonly XName bevelB = w14 + "bevelB";
+ public static readonly XName bevelT = w14 + "bevelT";
+ public static readonly XName blurRad = w14 + "blurRad";
+ public static readonly XName camera = w14 + "camera";
+ public static readonly XName cap = w14 + "cap";
+ public static readonly XName checkbox = w14 + "checkbox";
+ public static readonly XName _checked = w14 + "checked";
+ public static readonly XName checkedState = w14 + "checkedState";
+ public static readonly XName cmpd = w14 + "cmpd";
+ public static readonly XName cntxtAlts = w14 + "cntxtAlts";
+ public static readonly XName cNvContentPartPr = w14 + "cNvContentPartPr";
+ public static readonly XName conflictMode = w14 + "conflictMode";
+ public static readonly XName contentPart = w14 + "contentPart";
+ public static readonly XName contourClr = w14 + "contourClr";
+ public static readonly XName contourW = w14 + "contourW";
+ public static readonly XName defaultImageDpi = w14 + "defaultImageDpi";
+ public static readonly XName dir = w14 + "dir";
+ public static readonly XName discardImageEditingData = w14 + "discardImageEditingData";
+ public static readonly XName dist = w14 + "dist";
+ public static readonly XName docId = w14 + "docId";
+ public static readonly XName editId = w14 + "editId";
+ public static readonly XName enableOpenTypeKerning = w14 + "enableOpenTypeKerning";
+ public static readonly XName endA = w14 + "endA";
+ public static readonly XName endPos = w14 + "endPos";
+ public static readonly XName entityPicker = w14 + "entityPicker";
+ public static readonly XName extrusionClr = w14 + "extrusionClr";
+ public static readonly XName extrusionH = w14 + "extrusionH";
+ public static readonly XName fadeDir = w14 + "fadeDir";
+ public static readonly XName fillToRect = w14 + "fillToRect";
+ public static readonly XName font = w14 + "font";
+ public static readonly XName glow = w14 + "glow";
+ public static readonly XName gradFill = w14 + "gradFill";
+ public static readonly XName gs = w14 + "gs";
+ public static readonly XName gsLst = w14 + "gsLst";
+ public static readonly XName h = w14 + "h";
+ public static readonly XName hueMod = w14 + "hueMod";
+ public static readonly XName id = w14 + "id";
+ public static readonly XName kx = w14 + "kx";
+ public static readonly XName ky = w14 + "ky";
+ public static readonly XName l = w14 + "l";
+ public static readonly XName lat = w14 + "lat";
+ public static readonly XName ligatures = w14 + "ligatures";
+ public static readonly XName lightRig = w14 + "lightRig";
+ public static readonly XName lim = w14 + "lim";
+ public static readonly XName lin = w14 + "lin";
+ public static readonly XName lon = w14 + "lon";
+ public static readonly XName lumMod = w14 + "lumMod";
+ public static readonly XName lumOff = w14 + "lumOff";
+ public static readonly XName miter = w14 + "miter";
+ public static readonly XName noFill = w14 + "noFill";
+ public static readonly XName numForm = w14 + "numForm";
+ public static readonly XName numSpacing = w14 + "numSpacing";
+ public static readonly XName nvContentPartPr = w14 + "nvContentPartPr";
+ public static readonly XName paraId = w14 + "paraId";
+ public static readonly XName path = w14 + "path";
+ public static readonly XName pos = w14 + "pos";
+ public static readonly XName props3d = w14 + "props3d";
+ public static readonly XName prst = w14 + "prst";
+ public static readonly XName prstDash = w14 + "prstDash";
+ public static readonly XName prstMaterial = w14 + "prstMaterial";
+ public static readonly XName r = w14 + "r";
+ public static readonly XName rad = w14 + "rad";
+ public static readonly XName reflection = w14 + "reflection";
+ public static readonly XName rev = w14 + "rev";
+ public static readonly XName rig = w14 + "rig";
+ public static readonly XName rot = w14 + "rot";
+ public static readonly XName round = w14 + "round";
+ public static readonly XName sat = w14 + "sat";
+ public static readonly XName satMod = w14 + "satMod";
+ public static readonly XName satOff = w14 + "satOff";
+ public static readonly XName scaled = w14 + "scaled";
+ public static readonly XName scene3d = w14 + "scene3d";
+ public static readonly XName schemeClr = w14 + "schemeClr";
+ public static readonly XName shade = w14 + "shade";
+ public static readonly XName shadow = w14 + "shadow";
+ public static readonly XName solidFill = w14 + "solidFill";
+ public static readonly XName srgbClr = w14 + "srgbClr";
+ public static readonly XName stA = w14 + "stA";
+ public static readonly XName stPos = w14 + "stPos";
+ public static readonly XName styleSet = w14 + "styleSet";
+ public static readonly XName stylisticSets = w14 + "stylisticSets";
+ public static readonly XName sx = w14 + "sx";
+ public static readonly XName sy = w14 + "sy";
+ public static readonly XName t = w14 + "t";
+ public static readonly XName textFill = w14 + "textFill";
+ public static readonly XName textId = w14 + "textId";
+ public static readonly XName textOutline = w14 + "textOutline";
+ public static readonly XName tint = w14 + "tint";
+ public static readonly XName uncheckedState = w14 + "uncheckedState";
+ public static readonly XName val = w14 + "val";
+ public static readonly XName w = w14 + "w";
+ public static readonly XName wProps3d = w14 + "wProps3d";
+ public static readonly XName wScene3d = w14 + "wScene3d";
+ public static readonly XName wShadow = w14 + "wShadow";
+ public static readonly XName wTextFill = w14 + "wTextFill";
+ public static readonly XName wTextOutline = w14 + "wTextOutline";
+ public static readonly XName xfrm = w14 + "xfrm";
+ }
+
+ public static class W15
+ {
+ public static XNamespace w15 = "http://schemas.microsoft.com/office/word/2012/wordml";
+ }
+
+ public static class W16SE
+ {
+ public static XNamespace w16se = "http://schemas.microsoft.com/office/word/2015/wordml/symex";
+ }
+
+ public static class WE
+ {
+ public static readonly XNamespace we = "http://schemas.microsoft.com/office/webextensions/webextension/2010/11";
+ public static readonly XName alternateReferences = we + "alternateReferences";
+ public static readonly XName binding = we + "binding";
+ public static readonly XName bindings = we + "bindings";
+ public static readonly XName extLst = we + "extLst";
+ public static readonly XName properties = we + "properties";
+ public static readonly XName property = we + "property";
+ public static readonly XName reference = we + "reference";
+ public static readonly XName snapshot = we + "snapshot";
+ public static readonly XName web_extension = we + "web-extension";
+ public static readonly XName webextension = we + "webextension";
+ public static readonly XName webextensionref = we + "webextensionref";
+ }
+
+ public static class WETP
+ {
+ public static readonly XNamespace wetp = "http://schemas.microsoft.com/office/webextensions/taskpanes/2010/11";
+ public static readonly XName extLst = wetp + "extLst";
+ public static readonly XName taskpane = wetp + "taskpane";
+ public static readonly XName taskpanes = wetp + "taskpanes";
+ public static readonly XName web_extension_taskpanes = wetp + "web-extension-taskpanes";
+ public static readonly XName webextensionref = wetp + "webextensionref";
+ }
+
+ public static class W3DIGSIG
+ {
+ public static readonly XNamespace w3digsig =
+ "http://www.w3.org/2000/09/xmldsig#";
+ public static readonly XName CanonicalizationMethod = w3digsig + "CanonicalizationMethod";
+ public static readonly XName DigestMethod = w3digsig + "DigestMethod";
+ public static readonly XName DigestValue = w3digsig + "DigestValue";
+ public static readonly XName Exponent = w3digsig + "Exponent";
+ public static readonly XName KeyInfo = w3digsig + "KeyInfo";
+ public static readonly XName KeyValue = w3digsig + "KeyValue";
+ public static readonly XName Manifest = w3digsig + "Manifest";
+ public static readonly XName Modulus = w3digsig + "Modulus";
+ public static readonly XName Object = w3digsig + "Object";
+ public static readonly XName Reference = w3digsig + "Reference";
+ public static readonly XName RSAKeyValue = w3digsig + "RSAKeyValue";
+ public static readonly XName Signature = w3digsig + "Signature";
+ public static readonly XName SignatureMethod = w3digsig + "SignatureMethod";
+ public static readonly XName SignatureProperties = w3digsig + "SignatureProperties";
+ public static readonly XName SignatureProperty = w3digsig + "SignatureProperty";
+ public static readonly XName SignatureValue = w3digsig + "SignatureValue";
+ public static readonly XName SignedInfo = w3digsig + "SignedInfo";
+ public static readonly XName Transform = w3digsig + "Transform";
+ public static readonly XName Transforms = w3digsig + "Transforms";
+ public static readonly XName X509Certificate = w3digsig + "X509Certificate";
+ public static readonly XName X509Data = w3digsig + "X509Data";
+ public static readonly XName X509IssuerName = w3digsig + "X509IssuerName";
+ public static readonly XName X509SerialNumber = w3digsig + "X509SerialNumber";
+ }
+
+ public static class WP
+ {
+ public static readonly XNamespace wp =
+ "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing";
+ public static readonly XName align = wp + "align";
+ public static readonly XName anchor = wp + "anchor";
+ public static readonly XName cNvGraphicFramePr = wp + "cNvGraphicFramePr";
+ public static readonly XName docPr = wp + "docPr";
+ public static readonly XName effectExtent = wp + "effectExtent";
+ public static readonly XName extent = wp + "extent";
+ public static readonly XName inline = wp + "inline";
+ public static readonly XName lineTo = wp + "lineTo";
+ public static readonly XName positionH = wp + "positionH";
+ public static readonly XName positionV = wp + "positionV";
+ public static readonly XName posOffset = wp + "posOffset";
+ public static readonly XName simplePos = wp + "simplePos";
+ public static readonly XName start = wp + "start";
+ public static readonly XName wrapNone = wp + "wrapNone";
+ public static readonly XName wrapPolygon = wp + "wrapPolygon";
+ public static readonly XName wrapSquare = wp + "wrapSquare";
+ public static readonly XName wrapThrough = wp + "wrapThrough";
+ public static readonly XName wrapTight = wp + "wrapTight";
+ public static readonly XName wrapTopAndBottom = wp + "wrapTopAndBottom";
+ }
+
+ public static class WP14
+ {
+ public static readonly XNamespace wp14 =
+ "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing";
+ public static readonly XName anchorId = wp14 + "anchorId";
+ public static readonly XName editId = wp14 + "editId";
+ public static readonly XName pctHeight = wp14 + "pctHeight";
+ public static readonly XName pctPosVOffset = wp14 + "pctPosVOffset";
+ public static readonly XName pctWidth = wp14 + "pctWidth";
+ public static readonly XName sizeRelH = wp14 + "sizeRelH";
+ public static readonly XName sizeRelV = wp14 + "sizeRelV";
+ }
+
+ public static class WPS
+ {
+ public static readonly XNamespace wps =
+ "http://schemas.microsoft.com/office/word/2010/wordprocessingShape";
+ public static readonly XName altTxbx = wps + "altTxbx";
+ public static readonly XName bodyPr = wps + "bodyPr";
+ public static readonly XName cNvSpPr = wps + "cNvSpPr";
+ public static readonly XName spPr = wps + "spPr";
+ public static readonly XName style = wps + "style";
+ public static readonly XName textbox = wps + "textbox";
+ public static readonly XName txbx = wps + "txbx";
+ public static readonly XName wsp = wps + "wsp";
+ }
+
+ public static class X
+ {
+ public static readonly XNamespace x =
+ "urn:schemas-microsoft-com:office:excel";
+ public static readonly XName Anchor = x + "Anchor";
+ public static readonly XName AutoFill = x + "AutoFill";
+ public static readonly XName ClientData = x + "ClientData";
+ public static readonly XName Column = x + "Column";
+ public static readonly XName MoveWithCells = x + "MoveWithCells";
+ public static readonly XName Row = x + "Row";
+ public static readonly XName SizeWithCells = x + "SizeWithCells";
+ }
+
+ public static class XDR
+ {
+ public static readonly XNamespace xdr =
+ "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing";
+ public static readonly XName absoluteAnchor = xdr + "absoluteAnchor";
+ public static readonly XName blipFill = xdr + "blipFill";
+ public static readonly XName clientData = xdr + "clientData";
+ public static readonly XName cNvCxnSpPr = xdr + "cNvCxnSpPr";
+ public static readonly XName cNvGraphicFramePr = xdr + "cNvGraphicFramePr";
+ public static readonly XName cNvGrpSpPr = xdr + "cNvGrpSpPr";
+ public static readonly XName cNvPicPr = xdr + "cNvPicPr";
+ public static readonly XName cNvPr = xdr + "cNvPr";
+ public static readonly XName cNvSpPr = xdr + "cNvSpPr";
+ public static readonly XName col = xdr + "col";
+ public static readonly XName colOff = xdr + "colOff";
+ public static readonly XName contentPart = xdr + "contentPart";
+ public static readonly XName cxnSp = xdr + "cxnSp";
+ public static readonly XName ext = xdr + "ext";
+ public static readonly XName from = xdr + "from";
+ public static readonly XName graphicFrame = xdr + "graphicFrame";
+ public static readonly XName grpSp = xdr + "grpSp";
+ public static readonly XName grpSpPr = xdr + "grpSpPr";
+ public static readonly XName nvCxnSpPr = xdr + "nvCxnSpPr";
+ public static readonly XName nvGraphicFramePr = xdr + "nvGraphicFramePr";
+ public static readonly XName nvGrpSpPr = xdr + "nvGrpSpPr";
+ public static readonly XName nvPicPr = xdr + "nvPicPr";
+ public static readonly XName nvSpPr = xdr + "nvSpPr";
+ public static readonly XName oneCellAnchor = xdr + "oneCellAnchor";
+ public static readonly XName pic = xdr + "pic";
+ public static readonly XName pos = xdr + "pos";
+ public static readonly XName row = xdr + "row";
+ public static readonly XName rowOff = xdr + "rowOff";
+ public static readonly XName sp = xdr + "sp";
+ public static readonly XName spPr = xdr + "spPr";
+ public static readonly XName style = xdr + "style";
+ public static readonly XName to = xdr + "to";
+ public static readonly XName twoCellAnchor = xdr + "twoCellAnchor";
+ public static readonly XName txBody = xdr + "txBody";
+ public static readonly XName wsDr = xdr + "wsDr";
+ public static readonly XName xfrm = xdr + "xfrm";
+ }
+
+ public static class XDR14
+ {
+ public static readonly XNamespace xdr14 =
+ "http://schemas.microsoft.com/office/excel/2010/spreadsheetDrawing";
+ public static readonly XName cNvContentPartPr = xdr14 + "cNvContentPartPr";
+ public static readonly XName cNvPr = xdr14 + "cNvPr";
+ public static readonly XName nvContentPartPr = xdr14 + "nvContentPartPr";
+ public static readonly XName nvPr = xdr14 + "nvPr";
+ public static readonly XName xfrm = xdr14 + "xfrm";
+ }
+
+ public static class XM
+ {
+ public static readonly XNamespace xm =
+ "http://schemas.microsoft.com/office/excel/2006/main";
+ public static readonly XName f = xm + "f";
+ public static readonly XName _ref = xm + "ref";
+ public static readonly XName sqref = xm + "sqref";
+ }
+
+ public static class XSI
+ {
+ public static readonly XNamespace xsi =
+ "http://www.w3.org/2001/XMLSchema-instance";
+ public static readonly XName type = xsi + "type";
+ }
+
+
+ /************************************* end generated classes *************************************/
+
+ public static class PtOpenXml
+ {
+ public static XNamespace ptOpenXml = "http://powertools.codeplex.com/documentbuilder/2011/insert";
+ public static XName Insert = ptOpenXml + "Insert";
+ public static XName Id = "Id";
+
+ public static XNamespace pt = "http://powertools.codeplex.com/2011";
+ public static XName Uri = pt + "Uri";
+ public static XName Unid = pt + "Unid";
+ public static XName SHA1Hash = pt + "SHA1Hash";
+ public static XName CorrelatedSHA1Hash = pt + "CorrelatedSHA1Hash";
+ public static XName StructureSHA1Hash = pt + "StructureSHA1Hash";
+ public static XName CorrelationSet = pt + "CorrelationSet";
+ public static XName Status = pt + "Status";
+
+ public static XName Level = pt + "Level";
+ public static XName IndentLevel = pt + "IndentLevel";
+ public static XName ContentType = pt + "ContentType";
+
+ public static XName trPr = pt + "trPr";
+ public static XName tcPr = pt + "tcPr";
+ public static XName rPr = pt + "rPr";
+ public static XName pPr = pt + "pPr";
+ public static XName tblPr = pt + "tblPr";
+ public static XName style = pt + "style";
+
+ public static XName FontName = pt + "FontName";
+ public static XName LanguageType = pt + "LanguageType";
+ public static XName AbstractNumId = pt + "AbstractNumId";
+ public static XName StyleName = pt + "StyleName";
+ public static XName TabWidth = pt + "TabWidth";
+ public static XName Leader = pt + "Leader";
+
+ public static XName ListItemRun = pt + "ListItemRun";
+
+ public static XName HtmlToWmlCssWidth = pt + "HtmlToWmlCssWidth";
+ }
+
+ public static class Xhtml
+ {
+ public static readonly XNamespace xhtml = "http://www.w3.org/1999/xhtml";
+ public static readonly XName a = xhtml + "a";
+ public static readonly XName b = xhtml + "b";
+ public static readonly XName body = xhtml + "body";
+ public static readonly XName br = xhtml + "br";
+ public static readonly XName div = xhtml + "div";
+ public static readonly XName h1 = xhtml + "h1";
+ public static readonly XName h2 = xhtml + "h2";
+ public static readonly XName head = xhtml + "head";
+ public static readonly XName html = xhtml + "html";
+ public static readonly XName i = xhtml + "i";
+ public static readonly XName img = xhtml + "img";
+ public static readonly XName meta = xhtml + "meta";
+ public static readonly XName p = xhtml + "p";
+ public static readonly XName s = xhtml + "s";
+ public static readonly XName span = xhtml + "span";
+ public static readonly XName style = xhtml + "style";
+ public static readonly XName sub = xhtml + "sub";
+ public static readonly XName sup = xhtml + "sup";
+ public static readonly XName table = xhtml + "table";
+ public static readonly XName td = xhtml + "td";
+ public static readonly XName title = xhtml + "title";
+ public static readonly XName tr = xhtml + "tr";
+ public static readonly XName u = xhtml + "u";
+ }
+
+ public static class XhtmlNoNamespace
+ {
+ public static XNamespace xhtml = XNamespace.None;
+ public static XName html = xhtml + "html";
+ public static XName head = xhtml + "head";
+ public static XName title = xhtml + "title";
+ public static XName _class = xhtml + "class";
+ public static XName colspan = xhtml + "colspan";
+ public static XName caption = xhtml + "caption";
+ public static XName body = xhtml + "body";
+ public static XName div = xhtml + "div";
+ public static XName p = xhtml + "p";
+ public static XName h1 = xhtml + "h1";
+ public static XName h2 = xhtml + "h2";
+ public static XName h3 = xhtml + "h3";
+ public static XName h4 = xhtml + "h4";
+ public static XName h5 = xhtml + "h5";
+ public static XName h6 = xhtml + "h6";
+ public static XName h7 = xhtml + "h7";
+ public static XName h8 = xhtml + "h8";
+ public static XName h9 = xhtml + "h9";
+ public static XName hr = xhtml + "hr";
+ public static XName a = xhtml + "a";
+ public static XName b = xhtml + "b";
+ public static XName i = xhtml + "i";
+ public static XName table = xhtml + "table";
+ public static XName th = xhtml + "th";
+ public static XName tr = xhtml + "tr";
+ public static XName td = xhtml + "td";
+ public static XName meta = xhtml + "meta";
+ public static XName style = xhtml + "style";
+ public static XName br = xhtml + "br";
+ public static XName img = xhtml + "img";
+ public static XName span = xhtml + "span";
+ public static XName href = "href";
+ public static XName border = "border";
+ public static XName http_equiv = "http-equiv";
+ public static XName content = "content";
+ public static XName name = "name";
+ public static XName width = "width";
+ public static XName height = "height";
+ public static XName src = "src";
+ public static XName alt = "alt";
+ public static XName id = "id";
+ public static XName descr = "descr";
+ public static XName blockquote = "blockquote";
+ public static XName type = "type";
+ public static XName sub = "sub";
+ public static XName sup = "sup";
+ public static XName ol = "ol";
+ public static XName ul = "ul";
+ public static XName li = "li";
+ public static XName strong = "Bold";
+ public static XName em = "em";
+ public static XName tbody = "tbody";
+ public static XName valign = "valign";
+ public static XName dir = "dir";
+ public static XName u = "u";
+ public static XName s = "s";
+ public static XName rowspan = "rowspan";
+ public static XName tt = "tt";
+ public static XName code = "code";
+ public static XName kbd = "kbd";
+ public static XName samp = "samp";
+ public static XName pre = "pre";
+ }
+
+ public class InvalidOpenXmlDocumentException : Exception
+ {
+ public InvalidOpenXmlDocumentException(string message) : base(message) { }
+ }
+
+ public class OpenXmlPowerToolsException : Exception
+ {
+ public OpenXmlPowerToolsException(string message) : base(message) { }
+ }
+
+ public class ColumnReferenceOutOfRange : Exception
+ {
+ public ColumnReferenceOutOfRange(string columnReference)
+ : base(string.Format("Column reference ({0}) is out of range.", columnReference))
+ {
+ }
+ }
+
+ public class WorksheetAlreadyExistsException : Exception
+ {
+ public WorksheetAlreadyExistsException(string sheetName)
+ : base(string.Format("The worksheet ({0}) already exists.", sheetName))
+ {
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/PtUtil.cs b/OpenXmlPowerTools/PtUtil.cs
new file mode 100644
index 0000000..22143b5
--- /dev/null
+++ b/OpenXmlPowerTools/PtUtil.cs
@@ -0,0 +1,1266 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 2.6.00
+ * Enhancements to support HtmlConverter.cs
+
+***************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Schema;
+
+namespace OpenXmlPowerTools
+{
+ public static class PtUtils
+ {
+ public static string NormalizeDirName(string dirName)
+ {
+ string d = dirName.Replace('\\', '/');
+ if (d[dirName.Length - 1] != '/' && d[dirName.Length - 1] != '\\')
+ return d + "/";
+
+ return d;
+ }
+
+ public static string MakeValidXml(string p)
+ {
+ return p.Any(c => c < 0x20)
+ ? p.Select(c => c < 0x20 ? string.Format("_{0:X}_", (int) c) : c.ToString()).StringConcatenate()
+ : p;
+ }
+
+ public static void AddElementIfMissing(XDocument partXDoc, XElement existing, string newElement)
+ {
+ if (existing != null)
+ return;
+
+ XElement newXElement = XElement.Parse(newElement);
+ newXElement.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
+ if (partXDoc.Root != null) partXDoc.Root.Add(newXElement);
+ }
+ }
+
+ public class MhtParser
+ {
+ public string MimeVersion;
+ public string ContentType;
+ public MhtParserPart[] Parts;
+
+ public class MhtParserPart
+ {
+ public string ContentLocation;
+ public string ContentTransferEncoding;
+ public string ContentType;
+ public string CharSet;
+ public string Text;
+ public byte[] Binary;
+ }
+
+ public static MhtParser Parse(string src)
+ {
+ string mimeVersion = null;
+ string contentType = null;
+ string boundary = null;
+
+ string[] lines = src.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
+
+
+ var priambleKeyWords = new[]
+ {
+ "MIME-VERSION:",
+ "CONTENT-TYPE:",
+ };
+
+ var priamble = lines.TakeWhile(l =>
+ {
+ var s = l.ToUpper();
+ return priambleKeyWords.Any(pk => s.StartsWith(pk));
+ }).ToArray();
+
+ foreach (var item in priamble)
+ {
+ if (item.ToUpper().StartsWith("MIME-VERSION:"))
+ mimeVersion = item.Substring("MIME-VERSION:".Length).Trim();
+ else if (item.ToUpper().StartsWith("CONTENT-TYPE:"))
+ {
+ var contentTypeLine = item.Substring("CONTENT-TYPE:".Length).Trim();
+ var spl = contentTypeLine.Split(';').Select(z => z.Trim()).ToArray();
+ foreach (var s in spl)
+ {
+ if (s.StartsWith("boundary"))
+ {
+ var begText = "boundary=\"";
+ var begLen = begText.Length;
+ boundary = s.Substring(begLen, s.Length - begLen - 1).TrimStart('-');
+ continue;
+ }
+ if (contentType == null)
+ {
+ contentType = s;
+ continue;
+ }
+ throw new OpenXmlPowerToolsException("Unexpected content in MHTML");
+ }
+ }
+ }
+
+ var grouped = lines
+ .Skip(priamble.Length)
+ .GroupAdjacent(l =>
+ {
+ var b = l.TrimStart('-') == boundary;
+ return b;
+ })
+ .Where(g => g.Key == false)
+ .ToArray();
+
+ var parts = grouped.Select(rp =>
+ {
+ var partPriambleKeyWords = new[]
+ {
+ "CONTENT-LOCATION:",
+ "CONTENT-TRANSFER-ENCODING:",
+ "CONTENT-TYPE:",
+ };
+
+ var partPriamble = rp.TakeWhile(l =>
+ {
+ var s = l.ToUpper();
+ return partPriambleKeyWords.Any(pk => s.StartsWith(pk));
+ }).ToArray();
+
+ string contentLocation = null;
+ string contentTransferEncoding = null;
+ string partContentType = null;
+ string partCharSet = null;
+ byte[] partBinary = null;
+
+ foreach (var item in partPriamble)
+ {
+ if (item.ToUpper().StartsWith("CONTENT-LOCATION:"))
+ contentLocation = item.Substring("CONTENT-LOCATION:".Length).Trim();
+ else if (item.ToUpper().StartsWith("CONTENT-TRANSFER-ENCODING:"))
+ contentTransferEncoding = item.Substring("CONTENT-TRANSFER-ENCODING:".Length).Trim();
+ else if (item.ToUpper().StartsWith("CONTENT-TYPE:"))
+ partContentType = item.Substring("CONTENT-TYPE:".Length).Trim();
+ }
+
+ var blankLinesAtBeginning = rp
+ .Skip(partPriamble.Length)
+ .TakeWhile(l => l == "")
+ .Count();
+
+ var partText = rp
+ .Skip(partPriamble.Length)
+ .Skip(blankLinesAtBeginning)
+ .Select(l => l + Environment.NewLine)
+ .StringConcatenate();
+
+ if (partContentType != null && partContentType.Contains(";"))
+ {
+ string thisPartContentType = null;
+ var spl = partContentType.Split(';').Select(s => s.Trim()).ToArray();
+ foreach (var s in spl)
+ {
+ if (s.StartsWith("charset"))
+ {
+ var begText = "charset=\"";
+ var begLen = begText.Length;
+ partCharSet = s.Substring(begLen, s.Length - begLen - 1);
+ continue;
+ }
+ if (thisPartContentType == null)
+ {
+ thisPartContentType = s;
+ continue;
+ }
+ throw new OpenXmlPowerToolsException("Unexpected content in MHTML");
+ }
+ partContentType = thisPartContentType;
+ }
+
+ if (contentTransferEncoding != null && contentTransferEncoding.ToUpper() == "BASE64")
+ {
+ partBinary = Convert.FromBase64String(partText);
+ }
+
+ return new MhtParserPart()
+ {
+ ContentLocation = contentLocation,
+ ContentTransferEncoding = contentTransferEncoding,
+ ContentType = partContentType,
+ CharSet = partCharSet,
+ Text = partText,
+ Binary = partBinary,
+ };
+ })
+ .Where(p => p.ContentType != null)
+ .ToArray();
+
+ return new MhtParser()
+ {
+ ContentType = contentType,
+ MimeVersion = mimeVersion,
+ Parts = parts,
+ };
+ }
+ }
+
+ public class Normalizer
+ {
+ public static XDocument Normalize(XDocument source, XmlSchemaSet schema)
+ {
+ bool havePSVI = false;
+ // validate, throw errors, add PSVI information
+ if (schema != null)
+ {
+ source.Validate(schema, null, true);
+ havePSVI = true;
+ }
+ return new XDocument(
+ source.Declaration,
+ source.Nodes().Select(n =>
+ {
+ // Remove comments, processing instructions, and text nodes that are
+ // children of XDocument. Only white space text nodes are allowed as
+ // children of a document, so we can remove all text nodes.
+ if (n is XComment || n is XProcessingInstruction || n is XText)
+ return null;
+ XElement e = n as XElement;
+ if (e != null)
+ return NormalizeElement(e, havePSVI);
+ return n;
+ }
+ )
+ );
+ }
+
+ public static bool DeepEqualsWithNormalization(XDocument doc1, XDocument doc2,
+ XmlSchemaSet schemaSet)
+ {
+ XDocument d1 = Normalize(doc1, schemaSet);
+ XDocument d2 = Normalize(doc2, schemaSet);
+ return XNode.DeepEquals(d1, d2);
+ }
+
+ private static IEnumerable<XAttribute> NormalizeAttributes(XElement element,
+ bool havePSVI)
+ {
+ return element.Attributes()
+ .Where(a => !a.IsNamespaceDeclaration &&
+ a.Name != Xsi.schemaLocation &&
+ a.Name != Xsi.noNamespaceSchemaLocation)
+ .OrderBy(a => a.Name.NamespaceName)
+ .ThenBy(a => a.Name.LocalName)
+ .Select(
+ a =>
+ {
+ if (havePSVI)
+ {
+ var dt = a.GetSchemaInfo().SchemaType.TypeCode;
+ switch (dt)
+ {
+ case XmlTypeCode.Boolean:
+ return new XAttribute(a.Name, (bool)a);
+ case XmlTypeCode.DateTime:
+ return new XAttribute(a.Name, (DateTime)a);
+ case XmlTypeCode.Decimal:
+ return new XAttribute(a.Name, (decimal)a);
+ case XmlTypeCode.Double:
+ return new XAttribute(a.Name, (double)a);
+ case XmlTypeCode.Float:
+ return new XAttribute(a.Name, (float)a);
+ case XmlTypeCode.HexBinary:
+ case XmlTypeCode.Language:
+ return new XAttribute(a.Name,
+ ((string)a).ToLower());
+ }
+ }
+ return a;
+ }
+ );
+ }
+
+ private static XNode NormalizeNode(XNode node, bool havePSVI)
+ {
+ // trim comments and processing instructions from normalized tree
+ if (node is XComment || node is XProcessingInstruction)
+ return null;
+ XElement e = node as XElement;
+ if (e != null)
+ return NormalizeElement(e, havePSVI);
+ // Only thing left is XCData and XText, so clone them
+ return node;
+ }
+
+ private static XElement NormalizeElement(XElement element, bool havePSVI)
+ {
+ if (havePSVI)
+ {
+ var dt = element.GetSchemaInfo();
+ switch (dt.SchemaType.TypeCode)
+ {
+ case XmlTypeCode.Boolean:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ (bool)element);
+ case XmlTypeCode.DateTime:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ (DateTime)element);
+ case XmlTypeCode.Decimal:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ (decimal)element);
+ case XmlTypeCode.Double:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ (double)element);
+ case XmlTypeCode.Float:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ (float)element);
+ case XmlTypeCode.HexBinary:
+ case XmlTypeCode.Language:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ ((string)element).ToLower());
+ default:
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ element.Nodes().Select(n => NormalizeNode(n, havePSVI))
+ );
+ }
+ }
+ else
+ {
+ return new XElement(element.Name,
+ NormalizeAttributes(element, havePSVI),
+ element.Nodes().Select(n => NormalizeNode(n, havePSVI))
+ );
+ }
+ }
+ }
+
+ public class FileUtils
+ {
+ public static DirectoryInfo GetDateTimeStampedDirectoryInfo(string prefix)
+ {
+ DateTime now = DateTime.Now;
+ string dirName =
+ prefix +
+ string.Format("-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour,
+ now.Minute, now.Second);
+ return new DirectoryInfo(dirName);
+ }
+
+ public static FileInfo GetDateTimeStampedFileInfo(string prefix, string suffix)
+ {
+ DateTime now = DateTime.Now;
+ string fileName =
+ prefix +
+ string.Format("-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour,
+ now.Minute, now.Second) +
+ suffix;
+ return new FileInfo(fileName);
+ }
+
+ public static void ThreadSafeCreateDirectory(DirectoryInfo dir)
+ {
+ while (true)
+ {
+ if (dir.Exists)
+ break;
+
+ try
+ {
+ dir.Create();
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ public static void ThreadSafeCopy(FileInfo sourceFile, FileInfo destFile)
+ {
+ while (true)
+ {
+ if (destFile.Exists)
+ break;
+
+ try
+ {
+ File.Copy(sourceFile.FullName, destFile.FullName);
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+ public static void ThreadSafeCreateEmptyTextFileIfNotExist(FileInfo file)
+ {
+ while (true)
+ {
+ if (file.Exists)
+ break;
+
+ try
+ {
+ File.WriteAllText(file.FullName, "");
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+
+#if !NET35
+ internal static void ThreadSafeAppendAllLines(FileInfo file, string[] strings)
+ {
+ while (true)
+ {
+ try
+ {
+ File.AppendAllLines(file.FullName, strings);
+ break;
+ }
+ catch (IOException)
+ {
+ System.Threading.Thread.Sleep(50);
+ }
+ }
+ }
+#endif
+
+ public static List<string> GetFilesRecursive(DirectoryInfo dir, string searchPattern)
+ {
+ var fileList = new List<string>();
+ GetFilesRecursiveInternal(dir, searchPattern, fileList);
+ return fileList;
+ }
+
+ private static void GetFilesRecursiveInternal(DirectoryInfo dir, string searchPattern, List<string> fileList)
+ {
+ fileList.AddRange(dir.GetFiles(searchPattern).Select(file => file.FullName));
+ foreach (DirectoryInfo subdir in dir.GetDirectories())
+ GetFilesRecursiveInternal(subdir, searchPattern, fileList);
+ }
+
+ public static List<string> GetFilesRecursive(DirectoryInfo dir)
+ {
+ var fileList = new List<string>();
+ GetFilesRecursiveInternal(dir, fileList);
+ return fileList;
+ }
+
+ private static void GetFilesRecursiveInternal(DirectoryInfo dir, List<string> fileList)
+ {
+ fileList.AddRange(dir.GetFiles().Select(file => file.FullName));
+ foreach (DirectoryInfo subdir in dir.GetDirectories())
+ GetFilesRecursiveInternal(subdir, fileList);
+ }
+
+ public static void CopyStream(Stream source, Stream target)
+ {
+ const int bufSize = 0x4096;
+ var buf = new byte[bufSize];
+ int bytesRead;
+ while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
+ target.Write(buf, 0, bytesRead);
+ }
+ }
+
+ public static class PtExtensions
+ {
+ public static XElement GetXElement(this XmlNode node)
+ {
+ var xDoc = new XDocument();
+ using (XmlWriter xmlWriter = xDoc.CreateWriter())
+ node.WriteTo(xmlWriter);
+ return xDoc.Root;
+ }
+
+ public static XmlNode GetXmlNode(this XElement element)
+ {
+ var xmlDoc = new XmlDocument();
+ using (XmlReader xmlReader = element.CreateReader())
+ xmlDoc.Load(xmlReader);
+ return xmlDoc;
+ }
+
+ public static XDocument GetXDocument(this XmlDocument document)
+ {
+ var xDoc = new XDocument();
+ using (XmlWriter xmlWriter = xDoc.CreateWriter())
+ document.WriteTo(xmlWriter);
+
+ XmlDeclaration decl = document.ChildNodes.OfType<XmlDeclaration>().FirstOrDefault();
+ if (decl != null)
+ xDoc.Declaration = new XDeclaration(decl.Version, decl.Encoding, decl.Standalone);
+
+ return xDoc;
+ }
+
+ public static XmlDocument GetXmlDocument(this XDocument document)
+ {
+ var xmlDoc = new XmlDocument();
+ using (XmlReader xmlReader = document.CreateReader())
+ {
+ xmlDoc.Load(xmlReader);
+ if (document.Declaration != null)
+ {
+ XmlDeclaration dec = xmlDoc.CreateXmlDeclaration(document.Declaration.Version,
+ document.Declaration.Encoding, document.Declaration.Standalone);
+ xmlDoc.InsertBefore(dec, xmlDoc.FirstChild);
+ }
+ }
+ return xmlDoc;
+ }
+
+ public static string StringConcatenate(this IEnumerable<string> source)
+ {
+ return source.Aggregate(
+ new StringBuilder(),
+ (sb, s) => sb.Append(s),
+ sb => sb.ToString());
+ }
+
+ public static string StringConcatenate<T>(this IEnumerable<T> source, Func<T, string> projectionFunc)
+ {
+ return source.Aggregate(
+ new StringBuilder(),
+ (sb, i) => sb.Append(projectionFunc(i)),
+ sb => sb.ToString());
+ }
+
+ public static IEnumerable<TResult> PtZip<TFirst, TSecond, TResult>(
+ this IEnumerable<TFirst> first,
+ IEnumerable<TSecond> second,
+ Func<TFirst, TSecond, TResult> func)
+ {
+ using (IEnumerator<TFirst> ie1 = first.GetEnumerator())
+ using (IEnumerator<TSecond> ie2 = second.GetEnumerator())
+ while (ie1.MoveNext() && ie2.MoveNext())
+ yield return func(ie1.Current, ie2.Current);
+ }
+
+ public static IEnumerable<IGrouping<TKey, TSource>> GroupAdjacent<TSource, TKey>(
+ this IEnumerable<TSource> source,
+ Func<TSource, TKey> keySelector)
+ {
+ TKey last = default(TKey);
+ var haveLast = false;
+ var list = new List<TSource>();
+
+ foreach (TSource s in source)
+ {
+ TKey k = keySelector(s);
+ if (haveLast)
+ {
+ if (!k.Equals(last))
+ {
+ yield return new GroupOfAdjacent<TSource, TKey>(list, last);
+
+ list = new List<TSource> { s };
+ last = k;
+ }
+ else
+ {
+ list.Add(s);
+ last = k;
+ }
+ }
+ else
+ {
+ list.Add(s);
+ last = k;
+ haveLast = true;
+ }
+ }
+ if (haveLast)
+ yield return new GroupOfAdjacent<TSource, TKey>(list, last);
+ }
+
+ private static void InitializeSiblingsReverseDocumentOrder(XElement element)
+ {
+ XElement prev = null;
+ foreach (XElement e in element.Elements())
+ {
+ e.AddAnnotation(new SiblingsReverseDocumentOrderInfo { PreviousSibling = prev });
+ prev = e;
+ }
+ }
+
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ public static IEnumerable<XElement> SiblingsBeforeSelfReverseDocumentOrder(
+ this XElement element)
+ {
+ if (element.Annotation<SiblingsReverseDocumentOrderInfo>() == null)
+ InitializeSiblingsReverseDocumentOrder(element.Parent);
+ XElement current = element;
+ while (true)
+ {
+ XElement previousElement = current
+ .Annotation<SiblingsReverseDocumentOrderInfo>()
+ .PreviousSibling;
+ if (previousElement == null)
+ yield break;
+
+ yield return previousElement;
+
+ current = previousElement;
+ }
+ }
+
+ private static void InitializeDescendantsReverseDocumentOrder(XElement element)
+ {
+ XElement prev = null;
+ foreach (XElement e in element.Descendants())
+ {
+ e.AddAnnotation(new DescendantsReverseDocumentOrderInfo { PreviousElement = prev });
+ prev = e;
+ }
+ }
+
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ public static IEnumerable<XElement> DescendantsBeforeSelfReverseDocumentOrder(
+ this XElement element)
+ {
+ if (element.Annotation<DescendantsReverseDocumentOrderInfo>() == null)
+ InitializeDescendantsReverseDocumentOrder(element.AncestorsAndSelf().Last());
+ XElement current = element;
+ while (true)
+ {
+ XElement previousElement = current
+ .Annotation<DescendantsReverseDocumentOrderInfo>()
+ .PreviousElement;
+ if (previousElement == null)
+ yield break;
+
+ yield return previousElement;
+
+ current = previousElement;
+ }
+ }
+
+ private static void InitializeDescendantsTrimmedReverseDocumentOrder(XElement element, XName trimName)
+ {
+ XElement prev = null;
+ foreach (XElement e in element.DescendantsTrimmed(trimName))
+ {
+ e.AddAnnotation(new DescendantsTrimmedReverseDocumentOrderInfo { PreviousElement = prev });
+ prev = e;
+ }
+ }
+
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ public static IEnumerable<XElement> DescendantsTrimmedBeforeSelfReverseDocumentOrder(
+ this XElement element, XName trimName)
+ {
+ if (element.Annotation<DescendantsTrimmedReverseDocumentOrderInfo>() == null)
+ {
+ XElement ances = element.AncestorsAndSelf(W.txbxContent).FirstOrDefault() ??
+ element.AncestorsAndSelf().Last();
+ InitializeDescendantsTrimmedReverseDocumentOrder(ances, trimName);
+ }
+
+ XElement current = element;
+ while (true)
+ {
+ XElement previousElement = current
+ .Annotation<DescendantsTrimmedReverseDocumentOrderInfo>()
+ .PreviousElement;
+ if (previousElement == null)
+ yield break;
+
+ yield return previousElement;
+
+ current = previousElement;
+ }
+ }
+
+ public static string ToStringNewLineOnAttributes(this XElement element)
+ {
+ var settings = new XmlWriterSettings
+ {
+ Indent = true,
+ OmitXmlDeclaration = true,
+ NewLineOnAttributes = true
+ };
+ var stringBuilder = new StringBuilder();
+ using (var stringWriter = new StringWriter(stringBuilder))
+ using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
+ element.WriteTo(xmlWriter);
+ return stringBuilder.ToString();
+ }
+
+ public static IEnumerable<XElement> DescendantsTrimmed(this XElement element,
+ XName trimName)
+ {
+ return DescendantsTrimmed(element, e => e.Name == trimName);
+ }
+
+ public static IEnumerable<XElement> DescendantsTrimmed(this XElement element,
+ Func<XElement, bool> predicate)
+ {
+ Stack<IEnumerator<XElement>> iteratorStack = new Stack<IEnumerator<XElement>>();
+ iteratorStack.Push(element.Elements().GetEnumerator());
+ while (iteratorStack.Count > 0)
+ {
+ while (iteratorStack.Peek().MoveNext())
+ {
+ XElement currentXElement = iteratorStack.Peek().Current;
+ if (predicate(currentXElement))
+ {
+ yield return currentXElement;
+ continue;
+ }
+ yield return currentXElement;
+ iteratorStack.Push(currentXElement.Elements().GetEnumerator());
+ }
+ iteratorStack.Pop();
+ }
+ }
+
+ public static IEnumerable<TResult> Rollup<TSource, TResult>(
+ this IEnumerable<TSource> source,
+ TResult seed,
+ Func<TSource, TResult, TResult> projection)
+ {
+ TResult nextSeed = seed;
+ foreach (TSource src in source)
+ {
+ TResult projectedValue = projection(src, nextSeed);
+ nextSeed = projectedValue;
+ yield return projectedValue;
+ }
+ }
+
+ public static IEnumerable<TResult> Rollup<TSource, TResult>(
+ this IEnumerable<TSource> source,
+ TResult seed,
+ Func<TSource, TResult, int, TResult> projection)
+ {
+ TResult nextSeed = seed;
+ int index = 0;
+ foreach (TSource src in source)
+ {
+ TResult projectedValue = projection(src, nextSeed, index++);
+ nextSeed = projectedValue;
+ yield return projectedValue;
+ }
+ }
+
+ public static IEnumerable<TSource> SequenceAt<TSource>(this TSource[] source, int index)
+ {
+ int i = index;
+ while (i < source.Length)
+ yield return source[i++];
+ }
+
+ public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
+ {
+ var saveList = new Queue<T>();
+ var saved = 0;
+ foreach (T item in source)
+ {
+ if (saved < count)
+ {
+ saveList.Enqueue(item);
+ ++saved;
+ continue;
+ }
+
+ saveList.Enqueue(item);
+ yield return saveList.Dequeue();
+ }
+ }
+
+ public static bool? ToBoolean(this XAttribute a)
+ {
+ if (a == null)
+ return null;
+
+ string s = ((string) a).ToLower();
+ switch (s)
+ {
+ case "1":
+ return true;
+ case "0":
+ return false;
+ case "true":
+ return true;
+ case "false":
+ return false;
+ case "on":
+ return true;
+ case "off":
+ return false;
+ default:
+ return (bool) a;
+ }
+ }
+
+ private static string GetQName(XElement xe)
+ {
+ string prefix = xe.GetPrefixOfNamespace(xe.Name.Namespace);
+ if (xe.Name.Namespace == XNamespace.None || prefix == null)
+ return xe.Name.LocalName;
+
+ return prefix + ":" + xe.Name.LocalName;
+ }
+
+ private static string GetQName(XAttribute xa)
+ {
+ string prefix = xa.Parent != null ? xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace) : null;
+ if (xa.Name.Namespace == XNamespace.None || prefix == null)
+ return xa.Name.ToString();
+
+ return prefix + ":" + xa.Name.LocalName;
+ }
+
+ private static string NameWithPredicate(XElement el)
+ {
+ if (el.Parent != null && el.Parent.Elements(el.Name).Count() != 1)
+ return GetQName(el) + "[" +
+ (el.ElementsBeforeSelf(el.Name).Count() + 1) + "]";
+ else
+ return GetQName(el);
+ }
+
+ public static string StrCat<T>(this IEnumerable<T> source,
+ string separator)
+ {
+ return source.Aggregate(new StringBuilder(),
+ (sb, i) => sb
+ .Append(i.ToString())
+ .Append(separator),
+ s => s.ToString());
+ }
+
+ public static string GetXPath(this XObject xobj)
+ {
+ if (xobj.Parent == null)
+ {
+ var doc = xobj as XDocument;
+ if (doc != null)
+ return ".";
+
+ var el = xobj as XElement;
+ if (el != null)
+ return "/" + NameWithPredicate(el);
+
+ var xt = xobj as XText;
+ if (xt != null)
+ return null;
+
+ //
+ //the following doesn't work because the XPath data
+ //model doesn't include white space text nodes that
+ //are children of the document.
+ //
+ //return
+ // "/" +
+ // (
+ // xt
+ // .Document
+ // .Nodes()
+ // .OfType<XText>()
+ // .Count() != 1 ?
+ // "text()[" +
+ // (xt
+ // .NodesBeforeSelf()
+ // .OfType<XText>()
+ // .Count() + 1) + "]" :
+ // "text()"
+ // );
+ //
+ var com = xobj as XComment;
+ if (com != null && com.Document != null)
+ return
+ "/" +
+ (
+ com
+ .Document
+ .Nodes()
+ .OfType<XComment>()
+ .Count() != 1
+ ? "comment()[" +
+ (com
+ .NodesBeforeSelf()
+ .OfType<XComment>()
+ .Count() + 1) +
+ "]"
+ : "comment()"
+ );
+
+ var pi = xobj as XProcessingInstruction;
+ if (pi != null)
+ return
+ "/" +
+ (
+ pi.Document != null && pi.Document.Nodes().OfType<XProcessingInstruction>().Count() != 1
+ ? "processing-instruction()[" +
+ (pi
+ .NodesBeforeSelf()
+ .OfType<XProcessingInstruction>()
+ .Count() + 1) +
+ "]"
+ : "processing-instruction()"
+ );
+
+ return null;
+ }
+ else
+ {
+ var el = xobj as XElement;
+ if (el != null)
+ {
+ return
+ "/" +
+ el
+ .Ancestors()
+ .InDocumentOrder()
+ .Select(e => NameWithPredicate(e))
+ .StrCat("/") +
+ NameWithPredicate(el);
+ }
+
+ var at = xobj as XAttribute;
+ if (at != null && at.Parent != null)
+ return
+ "/" +
+ at
+ .Parent
+ .AncestorsAndSelf()
+ .InDocumentOrder()
+ .Select(e => NameWithPredicate(e))
+ .StrCat("/") +
+ "@" + GetQName(at);
+
+ var com = xobj as XComment;
+ if (com != null && com.Parent != null)
+ return
+ "/" +
+ com
+ .Parent
+ .AncestorsAndSelf()
+ .InDocumentOrder()
+ .Select(e => NameWithPredicate(e))
+ .StrCat("/") +
+ (
+ com
+ .Parent
+ .Nodes()
+ .OfType<XComment>()
+ .Count() != 1
+ ? "comment()[" +
+ (com
+ .NodesBeforeSelf()
+ .OfType<XComment>()
+ .Count() + 1) + "]"
+ : "comment()"
+ );
+
+ var cd = xobj as XCData;
+ if (cd != null && cd.Parent != null)
+ return
+ "/" +
+ cd
+ .Parent
+ .AncestorsAndSelf()
+ .InDocumentOrder()
+ .Select(e => NameWithPredicate(e))
+ .StrCat("/") +
+ (
+ cd
+ .Parent
+ .Nodes()
+ .OfType<XText>()
+ .Count() != 1
+ ? "text()[" +
+ (cd
+ .NodesBeforeSelf()
+ .OfType<XText>()
+ .Count() + 1) + "]"
+ : "text()"
+ );
+
+ var tx = xobj as XText;
+ if (tx != null && tx.Parent != null)
+ return
+ "/" +
+ tx
+ .Parent
+ .AncestorsAndSelf()
+ .InDocumentOrder()
+ .Select(e => NameWithPredicate(e))
+ .StrCat("/") +
+ (
+ tx
+ .Parent
+ .Nodes()
+ .OfType<XText>()
+ .Count() != 1
+ ? "text()[" +
+ (tx
+ .NodesBeforeSelf()
+ .OfType<XText>()
+ .Count() + 1) + "]"
+ : "text()"
+ );
+
+ var pi = xobj as XProcessingInstruction;
+ if (pi != null && pi.Parent != null)
+ return
+ "/" +
+ pi
+ .Parent
+ .AncestorsAndSelf()
+ .InDocumentOrder()
+ .Select(e => NameWithPredicate(e))
+ .StrCat("/") +
+ (
+ pi
+ .Parent
+ .Nodes()
+ .OfType<XProcessingInstruction>()
+ .Count() != 1
+ ? "processing-instruction()[" +
+ (pi
+ .NodesBeforeSelf()
+ .OfType<XProcessingInstruction>()
+ .Count() + 1) + "]"
+ : "processing-instruction()"
+ );
+
+ return null;
+ }
+ }
+ }
+
+ public class ExecutableRunner
+ {
+ public class RunResults
+ {
+ public int ExitCode;
+ public Exception RunException;
+ public StringBuilder Output;
+ public StringBuilder Error;
+ }
+
+ public static RunResults RunExecutable(string executablePath, string arguments, string workingDirectory)
+ {
+ RunResults runResults = new RunResults
+ {
+ Output = new StringBuilder(),
+ Error = new StringBuilder(),
+ RunException = null
+ };
+ try
+ {
+ if (File.Exists(executablePath))
+ {
+ using (Process proc = new Process())
+ {
+ proc.StartInfo.FileName = executablePath;
+ proc.StartInfo.Arguments = arguments;
+ proc.StartInfo.WorkingDirectory = workingDirectory;
+ proc.StartInfo.UseShellExecute = false;
+ proc.StartInfo.RedirectStandardOutput = true;
+ proc.StartInfo.RedirectStandardError = true;
+ proc.OutputDataReceived +=
+ (o, e) => runResults.Output.Append(e.Data).Append(Environment.NewLine);
+ proc.ErrorDataReceived +=
+ (o, e) => runResults.Error.Append(e.Data).Append(Environment.NewLine);
+ proc.Start();
+ proc.BeginOutputReadLine();
+ proc.BeginErrorReadLine();
+ proc.WaitForExit();
+ runResults.ExitCode = proc.ExitCode;
+ }
+ }
+ else
+ {
+ throw new ArgumentException("Invalid executable path.", "executablePath");
+ }
+ }
+ catch (Exception e)
+ {
+ runResults.RunException = e;
+ }
+ return runResults;
+ }
+ }
+
+ public class SiblingsReverseDocumentOrderInfo
+ {
+ public XElement PreviousSibling;
+ }
+
+ public class DescendantsReverseDocumentOrderInfo
+ {
+ public XElement PreviousElement;
+ }
+
+ public class DescendantsTrimmedReverseDocumentOrderInfo
+ {
+ public XElement PreviousElement;
+ }
+
+ public class GroupOfAdjacent<TSource, TKey> : IGrouping<TKey, TSource>
+ {
+ public GroupOfAdjacent(List<TSource> source, TKey key)
+ {
+ GroupList = source;
+ Key = key;
+ }
+
+ public TKey Key { get; set; }
+ private List<TSource> GroupList { get; set; }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable<TSource>) this).GetEnumerator();
+ }
+
+ IEnumerator<TSource> IEnumerable<TSource>.GetEnumerator()
+ {
+ return ((IEnumerable<TSource>) GroupList).GetEnumerator();
+ }
+ }
+
+ public static class PtBucketTimer
+ {
+ private class BucketInfo
+ {
+ public int Count;
+ public TimeSpan Time;
+ }
+
+ private static string LastBucket = null;
+ private static DateTime LastTime;
+ private static Dictionary<string, BucketInfo> Buckets;
+
+ public static void Bucket(string bucket)
+ {
+ DateTime now = DateTime.Now;
+ if (LastBucket != null)
+ {
+ TimeSpan d = now - LastTime;
+ if (Buckets.ContainsKey(LastBucket))
+ {
+ Buckets[LastBucket].Count = Buckets[LastBucket].Count + 1;
+ Buckets[LastBucket].Time += d;
+ }
+ else
+ {
+ Buckets.Add(LastBucket, new BucketInfo()
+ {
+ Count = 1,
+ Time = d,
+ });
+ }
+ }
+ LastBucket = bucket;
+ LastTime = now;
+ }
+
+ public static string DumpBucketsByKey()
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (var bucket in Buckets.OrderBy(b => b.Key))
+ {
+ string ts = bucket.Value.Time.ToString();
+ if (ts.Contains('.'))
+ ts = ts.Substring(0, ts.Length - 5);
+ string s = bucket.Key.PadRight(60, '-') + " " + string.Format("{0:00000000}", bucket.Value.Count) + " " + ts;
+ sb.Append(s + Environment.NewLine);
+ }
+ TimeSpan total = Buckets
+ .Aggregate(TimeSpan.Zero, (t, b) => t + b.Value.Time);
+ var tz = total.ToString();
+ sb.Append(string.Format("Total: {0}", tz.Substring(0, tz.Length - 5)));
+ return sb.ToString();
+ }
+
+ public static string DumpBucketsByTime()
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (var bucket in Buckets.OrderBy(b => b.Value.Time))
+ {
+ string ts = bucket.Value.Time.ToString();
+ if (ts.Contains('.'))
+ ts = ts.Substring(0, ts.Length - 5);
+ string s = bucket.Key.PadRight(60, '-') + " " + string.Format("{0:00000000}", bucket.Value.Count) + " " + ts;
+ sb.Append(s + Environment.NewLine);
+ }
+ TimeSpan total = Buckets
+ .Aggregate(TimeSpan.Zero, (t, b) => t + b.Value.Time);
+ var tz = total.ToString();
+ sb.Append(string.Format("Total: {0}", tz.Substring(0, tz.Length - 5)));
+ return sb.ToString();
+ }
+
+ public static void Init()
+ {
+ Buckets = new Dictionary<string, BucketInfo>();
+ }
+ }
+
+ public class XEntity : XText
+ {
+ public override void WriteTo(XmlWriter writer)
+ {
+ if (Value.Substring(0, 1) == "#")
+ {
+ string e = string.Format("&{0};", Value);
+ writer.WriteRaw(e);
+ }
+ else
+ writer.WriteEntityRef(Value);
+ }
+
+ public XEntity(string value) : base(value)
+ {
+ }
+ }
+
+ public static class Xsi
+ {
+ public static XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
+
+ public static XName schemaLocation = xsi + "schemaLocation";
+ public static XName noNamespaceSchemaLocation = xsi + "noNamespaceSchemaLocation";
+ }
+}
diff --git a/OpenXmlPowerTools/ReferenceAdder.cs b/OpenXmlPowerTools/ReferenceAdder.cs
new file mode 100644
index 0000000..3b74972
--- /dev/null
+++ b/OpenXmlPowerTools/ReferenceAdder.cs
@@ -0,0 +1,654 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.XPath;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public partial class WmlDocument : OpenXmlPowerToolsDocument
+ {
+ public WmlDocument AddToc(string xPath, string switches, string title, int? rightTabPos)
+ {
+ return (WmlDocument)ReferenceAdder.AddToc(this, xPath, switches, title, rightTabPos);
+ }
+ public WmlDocument AddTof(string xPath, string switches, int? rightTabPos)
+ {
+ return (WmlDocument)ReferenceAdder.AddTof(this, xPath, switches, rightTabPos);
+ }
+ public WmlDocument AddToa(string xPath, string switches, int? rightTabPos)
+ {
+ return (WmlDocument)ReferenceAdder.AddToa(this, xPath, switches, rightTabPos);
+ }
+ }
+
+ public class ReferenceAdder
+ {
+ public static WmlDocument AddToc(WmlDocument document, string xPath, string switches, string title, int? rightTabPos)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ AddToc(doc, xPath, switches, title, rightTabPos);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void AddToc(WordprocessingDocument doc, string xPath, string switches, string title, int? rightTabPos)
+ {
+ UpdateFontTablePart(doc);
+ UpdateStylesPartForToc(doc);
+ UpdateStylesWithEffectsPartForToc(doc);
+
+ if (title == null)
+ title = "Contents";
+ if (rightTabPos == null)
+ rightTabPos = 9350;
+
+ // {0} tocTitle (default = "Contents")
+ // {1} rightTabPosition (default = 9350)
+ // {2} switches
+
+ String xmlString =
+@"<w:sdt xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:sdtPr>
+ <w:docPartObj>
+ <w:docPartGallery w:val='Table of Contents'/>
+ <w:docPartUnique/>
+ </w:docPartObj>
+ </w:sdtPr>
+ <w:sdtEndPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='minorHAnsi' w:cstheme='minorBidi' w:eastAsiaTheme='minorHAnsi' w:hAnsiTheme='minorHAnsi'/>
+ <w:color w:val='auto'/>
+ <w:sz w:val='22'/>
+ <w:szCs w:val='22'/>
+ <w:lang w:eastAsia='en-US'/>
+ </w:rPr>
+ </w:sdtEndPr>
+ <w:sdtContent>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val='TOCHeading'/>
+ </w:pPr>
+ <w:r>
+ <w:t>{0}</w:t>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val='TOC1'/>
+ <w:tabs>
+ <w:tab w:val='right' w:leader='dot' w:pos='{1}'/>
+ </w:tabs>
+ <w:rPr>
+ <w:noProof/>
+ </w:rPr>
+ </w:pPr>
+ <w:r>
+ <w:fldChar w:fldCharType='begin' w:dirty='true'/>
+ </w:r>
+ <w:r>
+ <w:instrText xml:space='preserve'> {2} </w:instrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType='separate'/>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:r>
+ <w:rPr>
+ <w:b/>
+ <w:bCs/>
+ <w:noProof/>
+ </w:rPr>
+ <w:fldChar w:fldCharType='end'/>
+ </w:r>
+ </w:p>
+ </w:sdtContent>
+</w:sdt>";
+
+ XmlReader sdtReader = XmlReader.Create(new StringReader(String.Format(xmlString, title, rightTabPos, switches)));
+ XElement sdt = XElement.Load(sdtReader);
+
+ XmlNamespaceManager namespaceManager;
+ XDocument mainXDoc = doc.MainDocumentPart.GetXDocument(out namespaceManager);
+ namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
+ XElement addBefore = mainXDoc.XPathSelectElement(xPath, namespaceManager);
+ if (addBefore == null)
+ throw new OpenXmlPowerToolsException("XPath expression did not select an element");
+
+ addBefore.AddBeforeSelf(sdt);
+ doc.MainDocumentPart.PutXDocument();
+
+ XDocument settingsXDoc = doc.MainDocumentPart.DocumentSettingsPart.GetXDocument();
+ XElement updateFields = settingsXDoc.Descendants(W.updateFields).FirstOrDefault();
+ if (updateFields != null)
+ updateFields.Attribute(W.val).Value = "true";
+ else
+ {
+ updateFields = new XElement(W.updateFields,
+ new XAttribute(W.val, "true"));
+ settingsXDoc.Root.Add(updateFields);
+ }
+ doc.MainDocumentPart.DocumentSettingsPart.PutXDocument();
+ }
+
+ public static WmlDocument AddTof(WmlDocument document, string xPath, string switches, int? rightTabPos)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ AddTof(doc, xPath, switches, rightTabPos);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void AddTof(WordprocessingDocument doc, string xPath, string switches, int? rightTabPos)
+ {
+ UpdateFontTablePart(doc);
+ UpdateStylesPartForTof(doc);
+ UpdateStylesWithEffectsPartForTof(doc);
+
+ if (rightTabPos == null)
+ rightTabPos = 9350;
+
+ // {0} rightTabPosition (default = 9350)
+ // {1} switches
+
+ string xmlString =
+@"<w:p xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:pPr>
+ <w:pStyle w:val='TableofFigures'/>
+ <w:tabs>
+ <w:tab w:val='right' w:leader='dot' w:pos='{0}'/>
+ </w:tabs>
+ <w:rPr>
+ <w:noProof/>
+ </w:rPr>
+ </w:pPr>
+ <w:r>
+ <w:fldChar w:fldCharType='begin' dirty='true'/>
+ </w:r>
+ <w:r>
+ <w:instrText xml:space='preserve'> {1} </w:instrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType='separate'/>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType='end'/>
+ </w:r>
+</w:p>";
+ XDocument mainXDoc = doc.MainDocumentPart.GetXDocument();
+
+ XmlReader paragraphReader = XmlReader.Create(new StringReader(String.Format(xmlString, rightTabPos, switches)));
+ XElement paragraph = XElement.Load(paragraphReader);
+ XmlNameTable nameTable = paragraphReader.NameTable;
+ XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
+ namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
+ XElement addBefore = mainXDoc.XPathSelectElement(xPath, namespaceManager);
+ if (addBefore == null)
+ throw new OpenXmlPowerToolsException("XPath expression did not select an element");
+
+ addBefore.AddBeforeSelf(paragraph);
+ doc.MainDocumentPart.PutXDocument();
+
+ XDocument settingsXDoc = doc.MainDocumentPart.DocumentSettingsPart.GetXDocument();
+ XElement updateFields = settingsXDoc.Descendants(W.updateFields).FirstOrDefault();
+ if (updateFields != null)
+ updateFields.Attribute(W.val).Value = "true";
+ else
+ {
+ updateFields = new XElement(W.updateFields,
+ new XAttribute(W.val, "true"));
+ settingsXDoc.Root.Add(updateFields);
+ }
+ doc.MainDocumentPart.DocumentSettingsPart.PutXDocument();
+ }
+
+ public static WmlDocument AddToa(WmlDocument document, string xPath, string switches, int? rightTabPos)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ AddToa(doc, xPath, switches, rightTabPos);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void AddToa(WordprocessingDocument doc, string xPath, string switches, int? rightTabPos)
+ {
+ UpdateFontTablePart(doc);
+ UpdateStylesPartForToa(doc);
+ UpdateStylesWithEffectsPartForToa(doc);
+
+ if (rightTabPos == null)
+ rightTabPos = 9350;
+
+ // {0} rightTabPosition (default = 9350)
+ // {1} switches
+
+ string xmlString =
+@"<w:p xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:pPr>
+ <w:pStyle w:val='TOAHeading'/>
+ <w:tabs>
+ <w:tab w:val='right'
+ w:leader='dot'
+ w:pos='{0}'/>
+ </w:tabs>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='minorHAnsi'
+ w:eastAsiaTheme='minorEastAsia'
+ w:hAnsiTheme='minorHAnsi'
+ w:cstheme='minorBidi'/>
+ <w:b w:val='0'/>
+ <w:bCs w:val='0'/>
+ <w:noProof/>
+ <w:sz w:val='22'/>
+ <w:szCs w:val='22'/>
+ </w:rPr>
+ </w:pPr>
+ <w:r>
+ <w:fldChar w:fldCharType='begin'/>
+ </w:r>
+ <w:r>
+ <w:instrText xml:space='preserve'> {1} </w:instrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType='separate'/>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType='end'/>
+ </w:r>
+</w:p>";
+
+ XDocument mainXDoc = doc.MainDocumentPart.GetXDocument();
+
+ XmlReader paragraphReader = XmlReader.Create(new StringReader(String.Format(xmlString, rightTabPos, switches)));
+ XElement paragraph = XElement.Load(paragraphReader);
+ XmlNameTable nameTable = paragraphReader.NameTable;
+ XmlNamespaceManager namespaceManager = new XmlNamespaceManager(nameTable);
+ namespaceManager.AddNamespace("w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main");
+ XElement addBefore = mainXDoc.XPathSelectElement(xPath, namespaceManager);
+ if (addBefore == null)
+ throw new OpenXmlPowerToolsException("XPath expression did not select an element");
+
+ addBefore.AddBeforeSelf(paragraph);
+ doc.MainDocumentPart.PutXDocument();
+
+ XDocument settingsXDoc = doc.MainDocumentPart.DocumentSettingsPart.GetXDocument();
+ XElement updateFields = settingsXDoc.Descendants(W.updateFields).FirstOrDefault();
+ if (updateFields != null)
+ updateFields.Attribute(W.val).Value = "true";
+ else
+ {
+ updateFields = new XElement(W.updateFields,
+ new XAttribute(W.val, "true"));
+ settingsXDoc.Root.Add(updateFields);
+ }
+ doc.MainDocumentPart.DocumentSettingsPart.PutXDocument();
+ }
+
+ private static void AddElementIfMissing(XDocument partXDoc, XElement existing, string newElement)
+ {
+ if (existing != null)
+ return;
+ XElement newXElement = XElement.Parse(newElement);
+ newXElement.Attributes().Where(a => a.IsNamespaceDeclaration).Remove();
+ partXDoc.Root.Add(newXElement);
+ }
+
+ private static void UpdateFontTablePart(WordprocessingDocument doc)
+ {
+ FontTablePart fontTablePart = doc.MainDocumentPart.FontTablePart;
+ if (fontTablePart == null)
+ throw new Exception("Todo need to insert font table part");
+ XDocument fontTableXDoc = fontTablePart.GetXDocument();
+
+ AddElementIfMissing(fontTableXDoc,
+ fontTableXDoc
+ .Root
+ .Elements(W.font)
+ .Where(e => (string)e.Attribute(W.name) == "Tahoma")
+ .FirstOrDefault(),
+ @"<w:font w:name='Tahoma' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:panose1 w:val='020B0604030504040204'/>
+ <w:charset w:val='00'/>
+ <w:family w:val='swiss'/>
+ <w:pitch w:val='variable'/>
+ <w:sig w:usb0='E1002EFF' w:usb1='C000605B' w:usb2='00000029' w:usb3='00000000' w:csb0='000101FF' w:csb1='00000000'/>
+ </w:font>");
+
+ fontTablePart.PutXDocument();
+ }
+
+ private static void UpdatePartForToc(OpenXmlPart part)
+ {
+ XDocument xDoc = part.GetXDocument();
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOCHeading")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='TOCHeading' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='TOC Heading'/>
+ <w:basedOn w:val='Heading1'/>
+ <w:next w:val='Normal'/>
+ <w:uiPriority w:val='39'/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:qFormat/>
+ <w:pPr>
+ <w:outlineLvl w:val='9'/>
+ </w:pPr>
+ <w:rPr>
+ <w:lang w:eastAsia='ja-JP'/>
+ </w:rPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC1")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='TOC1' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='toc 1'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:autoRedefine/>
+ <w:uiPriority w:val='39'/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after='100'/>
+ </w:pPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC2")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='TOC2' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='toc 2'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:autoRedefine/>
+ <w:uiPriority w:val='39'/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after='100'/>
+ <w:ind w:left='220'/>
+ </w:pPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC3")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='TOC3' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='toc 3'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:autoRedefine/>
+ <w:uiPriority w:val='39'/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after='100'/>
+ <w:ind w:left='440'/>
+ </w:pPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOC4")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='TOC4' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='toc 4'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:autoRedefine/>
+ <w:uiPriority w:val='39'/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after='100'/>
+ <w:ind w:left='660'/>
+ </w:pPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Hyperlink")
+ .FirstOrDefault(),
+ @"<w:style w:type='character' w:styleId='Hyperlink' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Hyperlink'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:uiPriority w:val='99'/>
+ <w:unhideWhenUsed/>
+ <w:rPr>
+ <w:color w:val='0000FF' w:themeColor='hyperlink'/>
+ <w:u w:val='single'/>
+ </w:rPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "BalloonText")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='BalloonText' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Balloon Text'/>
+ <w:basedOn w:val='Normal'/>
+ <w:link w:val='BalloonTextChar'/>
+ <w:uiPriority w:val='99'/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after='0' w:line='240' w:lineRule='auto'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:ascii='Tahoma' w:hAnsi='Tahoma' w:cs='Tahoma'/>
+ <w:sz w:val='16'/>
+ <w:szCs w:val='16'/>
+ </w:rPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" &&
+ (bool?)e.Attribute(W.customStyle) == true && (string)e.Attribute(W.styleId) == "BalloonTextChar")
+ .FirstOrDefault(),
+ @"<w:style w:type='character' w:customStyle='1' w:styleId='BalloonTextChar' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Balloon Text Char'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:link w:val='BalloonText'/>
+ <w:uiPriority w:val='99'/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:rFonts w:ascii='Tahoma' w:hAnsi='Tahoma' w:cs='Tahoma'/>
+ <w:sz w:val='16'/>
+ <w:szCs w:val='16'/>
+ </w:rPr>
+ </w:style>");
+
+ part.PutXDocument();
+ }
+
+ private static void UpdateStylesPartForToc(WordprocessingDocument doc)
+ {
+ StylesPart stylesPart = doc.MainDocumentPart.StyleDefinitionsPart;
+ if (stylesPart == null)
+ return;
+ UpdatePartForToc(stylesPart);
+ }
+
+ private static void UpdateStylesWithEffectsPartForToc(WordprocessingDocument doc)
+ {
+ StylesWithEffectsPart stylesWithEffectsPart = doc.MainDocumentPart.StylesWithEffectsPart;
+ if (stylesWithEffectsPart == null)
+ return;
+ UpdatePartForToc(stylesWithEffectsPart);
+ }
+
+ private static void UpdatePartForTof(OpenXmlPart part)
+ {
+ XDocument xDoc = part.GetXDocument();
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TableofFigures")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph' w:styleId='TableofFigures' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='table of figures'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:uiPriority w:val='99'/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after='0'/>
+ </w:pPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "character" && (string)e.Attribute(W.styleId) == "Hyperlink")
+ .FirstOrDefault(),
+ @"<w:style w:type='character' w:styleId='Hyperlink' xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='Hyperlink'/>
+ <w:basedOn w:val='DefaultParagraphFont'/>
+ <w:uiPriority w:val='99'/>
+ <w:unhideWhenUsed/>
+ <w:rPr>
+ <w:color w:val='0000FF' w:themeColor='hyperlink'/>
+ <w:u w:val='single'/>
+ </w:rPr>
+ </w:style>");
+ part.PutXDocument();
+ }
+
+ private static void UpdateStylesPartForTof(WordprocessingDocument doc)
+ {
+ StylesPart stylesPart = doc.MainDocumentPart.StyleDefinitionsPart;
+ if (stylesPart == null)
+ return;
+ UpdatePartForTof(stylesPart);
+ }
+
+ private static void UpdateStylesWithEffectsPartForTof(WordprocessingDocument doc)
+ {
+ StylesWithEffectsPart stylesWithEffectsPart = doc.MainDocumentPart.StylesWithEffectsPart;
+ if (stylesWithEffectsPart == null)
+ return;
+ UpdatePartForTof(stylesWithEffectsPart);
+ }
+
+ private static void UpdatePartForToa(OpenXmlPart part)
+ {
+ XDocument xDoc = part.GetXDocument();
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TableofAuthorities")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph'
+ w:styleId='TableofAuthorities'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='table of authorities'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:uiPriority w:val='99'/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:rsid w:val='0090569D'/>
+ <w:pPr>
+ <w:spacing w:after='0'/>
+ <w:ind w:left='220'
+ w:hanging='220'/>
+ </w:pPr>
+ </w:style>");
+
+ AddElementIfMissing(
+ xDoc,
+ xDoc.Root.Elements(W.style)
+ .Where(e => (string)e.Attribute(W.type) == "paragraph" && (string)e.Attribute(W.styleId) == "TOAHeading")
+ .FirstOrDefault(),
+ @"<w:style w:type='paragraph'
+ w:styleId='TOAHeading'
+ xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:name w:val='toa heading'/>
+ <w:basedOn w:val='Normal'/>
+ <w:next w:val='Normal'/>
+ <w:uiPriority w:val='99'/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:rsid w:val='0090569D'/>
+ <w:pPr>
+ <w:spacing w:before='120'/>
+ </w:pPr>
+ <w:rPr>
+ <w:rFonts w:asciiTheme='majorHAnsi'
+ w:eastAsiaTheme='majorEastAsia'
+ w:hAnsiTheme='majorHAnsi'
+ w:cstheme='majorBidi'/>
+ <w:b/>
+ <w:bCs/>
+ <w:sz w:val='24'/>
+ <w:szCs w:val='24'/>
+ </w:rPr>
+ </w:style>");
+
+ part.PutXDocument();
+ }
+
+ private static void UpdateStylesPartForToa(WordprocessingDocument doc)
+ {
+ StylesPart stylesPart = doc.MainDocumentPart.StyleDefinitionsPart;
+ if (stylesPart == null)
+ return;
+ UpdatePartForToa(stylesPart);
+ }
+
+ private static void UpdateStylesWithEffectsPartForToa(WordprocessingDocument doc)
+ {
+ StylesWithEffectsPart stylesWithEffectsPart = doc.MainDocumentPart.StylesWithEffectsPart;
+ if (stylesWithEffectsPart == null)
+ return;
+ UpdatePartForToa(stylesWithEffectsPart);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/RevisionAccepter.cs b/OpenXmlPowerTools/RevisionAccepter.cs
new file mode 100644
index 0000000..fed077f
--- /dev/null
+++ b/OpenXmlPowerTools/RevisionAccepter.cs
@@ -0,0 +1,61 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class RevisionAccepter
+ {
+ public static WmlDocument AcceptRevisions(WmlDocument document)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ AcceptRevisions(doc);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void AcceptRevisions(WordprocessingDocument doc)
+ {
+ RevisionProcessor.AcceptRevisions(doc);
+ }
+
+ public static bool PartHasTrackedRevisions(OpenXmlPart part)
+ {
+ return RevisionProcessor.PartHasTrackedRevisions(part);
+ }
+
+ public static bool HasTrackedRevisions(WmlDocument document)
+ {
+ return RevisionProcessor.HasTrackedRevisions(document);
+ }
+
+ public static bool HasTrackedRevisions(WordprocessingDocument doc)
+ {
+ return RevisionProcessor.HasTrackedRevisions(doc);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/RevisionProcessor.cs b/OpenXmlPowerTools/RevisionProcessor.cs
new file mode 100644
index 0000000..741263c
--- /dev/null
+++ b/OpenXmlPowerTools/RevisionProcessor.cs
@@ -0,0 +1,3253 @@
+/***************************************************************************
+
+Portions Copyright (c) Eric White 2017.
+Portions Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://EricWhite.com
+Resource Center and Documentation: http://ericwhite.com/blog/blog/open-xml-powertools-developer-center/
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com, ericwhitedev@gmail.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ class ReverseRevisionsInfo
+ {
+ public bool InInsert;
+ }
+
+ public class RevisionProcessor
+ {
+ public static WmlDocument RejectRevisions(WmlDocument document)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ RejectRevisions(doc);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void RejectRevisions(WordprocessingDocument doc)
+ {
+ RejectRevisionsForPart(doc.MainDocumentPart);
+ foreach (var part in doc.MainDocumentPart.HeaderParts)
+ RejectRevisionsForPart(part);
+ foreach (var part in doc.MainDocumentPart.FooterParts)
+ RejectRevisionsForPart(part);
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ RejectRevisionsForPart(doc.MainDocumentPart.EndnotesPart);
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ RejectRevisionsForPart(doc.MainDocumentPart.FootnotesPart);
+ if (doc.MainDocumentPart.StyleDefinitionsPart != null)
+ RejectRevisionsForStylesDefinitionPart(doc.MainDocumentPart.StyleDefinitionsPart);
+
+ ReverseRevisions(doc);
+ AcceptRevisionsForPart(doc.MainDocumentPart);
+ foreach (var part in doc.MainDocumentPart.HeaderParts)
+ AcceptRevisionsForPart(part);
+ foreach (var part in doc.MainDocumentPart.FooterParts)
+ AcceptRevisionsForPart(part);
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ AcceptRevisionsForPart(doc.MainDocumentPart.EndnotesPart);
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ AcceptRevisionsForPart(doc.MainDocumentPart.FootnotesPart);
+ if (doc.MainDocumentPart.StyleDefinitionsPart != null)
+ AcceptRevisionsForStylesDefinitionPart(doc.MainDocumentPart.StyleDefinitionsPart);
+ }
+
+ // Reject revisions for those revisions that can't be rejected by inverting the sense of the revision, and then accepting.
+ private static void RejectRevisionsForPart(OpenXmlPart part)
+ {
+ var xDoc = part.GetXDocument();
+ var newRoot = RejectRevisionsForPartTransform(xDoc.Root);
+ xDoc.Root.ReplaceWith(newRoot);
+ part.PutXDocument();
+ }
+
+ private static object RejectRevisionsForPartTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Inserted Numbering Properties
+#if false
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val="ListParagraph"/>
+ <w:numPr>
+ <w:ilvl w:val="0"/>
+ <w:numId w:val="1"/>
+ <w:ins w:id="0" w:author="Eric White" w:date="2017-03-26T03:50:00Z" />
+ </w:numPr>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ </w:pPr>
+ <w:r w:rsidRPr="009D59B3">
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>This is a test.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.numPr && element.Element(W.ins) != null)
+ return null;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Paragraph properties change
+#if false
+ <w:p>
+ <w:pPr>
+ <w:pStyle w:val="ListParagraph"/>
+ <w:numPr>
+ <w:ilvl w:val="1"/>
+ <w:numId w:val="2"/>
+ </w:numPr>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:pPrChange w:id="0" w:author="Eric White" w:date="2017-03-26T04:55:00Z">
+ <w:pPr>
+ <w:pStyle w:val="ListParagraph"/>
+ <w:numPr>
+ <w:ilvl w:val="1"/>
+ <w:numId w:val="1"/>
+ </w:numPr>
+ <w:ind w:left="1440" w:hanging="360"/>
+ </w:pPr>
+ </w:pPrChange>
+ </w:pPr>
+ <w:r>
+ <w:t>When you click Online Video, you can paste in the embed code for the video you want to add.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.pPr &&
+ element.Element(W.pPrChange) != null)
+ {
+ var pPr = element.Element(W.pPrChange).Element(W.pPr);
+ if (pPr == null)
+ pPr = new XElement(W.pPr);
+ var new_pPr = new XElement(pPr); // clone it
+ new_pPr.Add(RejectRevisionsForPartTransform(element.Element(W.rPr)));
+ return RejectRevisionsForPartTransform(new_pPr);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Run properties change
+#if false
+ <w:p w:rsidR="00615148" w:rsidRPr="00615148" w:rsidRDefault="00615148">
+ <w:pPr>
+ <w:rPr>
+ <w:b/>
+ <w:lang w:val="en-US"/>
+ <w:rPrChange w:id="0" w:author="Eric White" w:date="2017-03-26T05:02:00Z">
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ </w:rPrChange>
+ </w:rPr>
+ </w:pPr>
+ <w:r>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>When you click Online Video, you can paste in the embed code for the video you want to add.</w:t>
+ </w:r>
+ <w:bookmarkStart w:id="1" w:name="_GoBack"/>
+ </w:p>
+#endif
+ if (element.Name == W.rPr &&
+ element.Element(W.rPrChange) != null)
+ {
+ var new_rPr = element.Element(W.rPrChange).Element(W.rPr);
+ return RejectRevisionsForPartTransform(new_rPr);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Field code numbering change
+#if false
+ <w:p w:rsidR="00D46247" w:rsidRDefault="00D46247">
+ <w:r>
+ <w:fldChar w:fldCharType="begin"/>
+ </w:r>
+ <w:r>
+ <w:instrText xml:space="preserve"> LISTNUM </w:instrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="end">
+ <w:numberingChange w:id="0" w:author="Eric White" w:date="2017-03-26T12:48:00Z" w:original="1)"/>
+ </w:fldChar>
+ </w:r>
+ <w:r>
+ <w:t xml:space="preserve"> Video provides a powerful way to help you prove your point.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.numberingChange)
+ return null;
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Change w:sectPr
+#if false
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:ins w:id="0" w:author="Eric White" w:date="2017-03-26T15:40:00Z"/>
+ </w:rPr>
+ <w:sectPr>
+ <w:pgSz w:w="12240" w:h="15840"/>
+ <w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720" w:header="720" w:footer="720" w:gutter="0"/>
+ <w:cols w:space="720"/>
+ <w:docGrid w:linePitch="360"/>
+ <w:sectPrChange w:id="1" w:author="Eric White" w:date="2017-03-26T15:42:00Z">
+ <w:sectPr w:rsidR="00620990" w:rsidSect="004E0757">
+ <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" w:footer="720" w:gutter="0"/>
+ </w:sectPr>
+ </w:sectPrChange>
+ </w:sectPr>
+ </w:pPr>
+ </w:p>
+#endif
+ if (element.Name == W.sectPr &&
+ element.Element(W.sectPrChange) != null)
+ {
+ var newSectPr = element.Element(W.sectPrChange).Element(W.sectPr);
+ return RejectRevisionsForPartTransform(newSectPr);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // tblGridChange
+#if false
+ <w:tblGrid>
+ <w:gridCol w:w="1525"/>
+ <w:gridCol w:w="3005"/>
+ <w:gridCol w:w="3006"/>
+ <w:tblGridChange w:id="1">
+ <w:tblGrid>
+ <w:gridCol w:w="3005"/>
+ <w:gridCol w:w="3005"/>
+ <w:gridCol w:w="3006"/>
+ </w:tblGrid>
+ </w:tblGridChange>
+ </w:tblGrid>
+#endif
+ if (element.Name == W.tblGrid &&
+ element.Element(W.tblGridChange) != null)
+ {
+ var newTblGrid = element.Element(W.tblGridChange).Element(W.tblGrid);
+ return RejectRevisionsForPartTransform(newTblGrid);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // tcPrChange
+#if false
+ <w:tc>
+ <w:tcPr>
+ <w:tcW w:w="1525" w:type="dxa"/>
+ <w:tcPrChange w:id="2" w:author="Eric White" w:date="2017-03-26T18:01:00Z">
+ <w:tcPr>
+ <w:tcW w:w="3005" w:type="dxa"/>
+ </w:tcPr>
+ </w:tcPrChange>
+ </w:tcPr>
+ <w:p>
+ <w:r>
+ <w:t>1</w:t>
+ </w:r>
+ </w:p>
+ </w:tc>
+#endif
+ if (element.Name == W.tcPr &&
+ element.Element(W.tcPrChange) != null)
+ {
+ var newTcPr = element.Element(W.tcPrChange).Element(W.tcPr);
+ return RejectRevisionsForPartTransform(newTcPr);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // trPrChange
+ if (element.Name == W.trPr &&
+ element.Element(W.trPrChange) != null)
+ {
+ var newTrPr = element.Element(W.trPrChange).Element(W.trPr);
+ return RejectRevisionsForPartTransform(newTrPr);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // tblPrExChange
+#if false
+ <w:tblPrEx>
+ <w:tblW w:w="0" w:type="auto"/>
+ <w:tblPrExChange w:id="1" w:author="Eric White" w:date="2017-03-26T18:10:00Z">
+ <w:tblPrEx>
+ <w:tblW w:w="0" w:type="auto"/>
+ </w:tblPrEx>
+ </w:tblPrExChange>
+ </w:tblPrEx>
+#endif
+
+#if false
+ <w:tr w:rsidR="00097582" w:rsidTr="00F843C4">
+ <w:tblPrEx>
+ <w:tblW w:w="0" w:type="auto"/>
+ <w:tblBorders>
+ <w:top w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:left w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:bottom w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:right w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:insideH w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:insideV w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ </w:tblBorders>
+ <w:tblPrExChange w:id="1" w:author="Eric White" w:date="2017-03-26T20:38:00Z">
+ <w:tblPrEx>
+ <w:tblW w:w="0" w:type="auto"/>
+ <w:tblBorders>
+ <w:top w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:left w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:bottom w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:right w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:insideH w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ <w:insideV w:val="thickThinMediumGap" w:sz="24" w:space="0" w:color="auto"/>
+ </w:tblBorders>
+ </w:tblPrEx>
+ </w:tblPrExChange>
+ </w:tblPrEx>
+#endif
+ if (element.Name == W.tblPrEx &&
+ element.Element(W.tblPrExChange) != null)
+ {
+ var newTblPrEx = element.Element(W.tblPrExChange).Element(W.tblPrEx);
+ return RejectRevisionsForPartTransform(newTblPrEx);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // tblPrChange
+#if false
+ <w:tbl>
+ <w:tblPr>
+ <w:tblStyle w:val="GridTable4-Accent1"/>
+ <w:tblW w:w="0" w:type="auto"/>
+ <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
+ <w:tblPrChange w:id="0" w:author="Eric White" w:date="2017-03-26T20:05:00Z">
+ <w:tblPr>
+ <w:tblStyle w:val="TableGrid"/>
+ <w:tblW w:w="0" w:type="auto"/>
+ <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>
+ </w:tblPr>
+ </w:tblPrChange>
+ </w:tblPr>
+#endif
+ if (element.Name == W.tblPr &&
+ element.Element(W.tblPrChange) != null)
+ {
+ var newTrPr = element.Element(W.tblPrChange).Element(W.tblPr);
+ return RejectRevisionsForPartTransform(newTrPr);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // tblPrChange
+#if false
+ <w:tc>
+ <w:tcPr>
+ <w:tcW w:w="3005" w:type="dxa"/>
+ <w:cellDel w:id="8" w:author="Eric White" w:date="2017-03-26T21:12:00Z"/>
+ <w:tcPrChange w:id="9" w:author="Eric White" w:date="2017-03-26T21:12:00Z">
+ <w:tcPr>
+ <w:tcW w:w="3005" w:type="dxa"/>
+ <w:gridSpan w:val="2"/>
+ <w:cellDel w:id="10" w:author="Eric White" w:date="2017-03-26T21:12:00Z"/>
+ </w:tcPr>
+ </w:tcPrChange>
+ </w:tcPr>
+#endif
+
+ if (element.Name == W.cellDel ||
+ element.Name == W.cellMerge)
+ return null;
+
+ if (element.Name == W.tc &&
+ element.Elements(W.tcPr).Elements(W.cellIns).Any())
+ return null;
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RejectRevisionsForPartTransform(n)));
+ }
+ return node;
+ }
+
+ private static void RejectRevisionsForStylesDefinitionPart(StyleDefinitionsPart stylesDefinitionsPart)
+ {
+ var xDoc = stylesDefinitionsPart.GetXDocument();
+ var newRoot = RejectRevisionsForStylesTransform(xDoc.Root);
+ xDoc.Root.ReplaceWith(newRoot);
+ stylesDefinitionsPart.PutXDocument();
+ }
+
+ private static object RejectRevisionsForStylesTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.pPr &&
+ element.Element(W.pPrChange) != null)
+ {
+ var new_pPr = element.Element(W.pPrChange).Element(W.pPr);
+ return RejectRevisionsForStylesTransform(new_pPr);
+ }
+
+ if (element.Name == W.rPr &&
+ element.Element(W.rPrChange) != null)
+ {
+ var new_rPr = element.Element(W.rPrChange).Element(W.rPr);
+ return RejectRevisionsForStylesTransform(new_rPr);
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RejectRevisionsForStylesTransform(n)));
+ }
+ return node;
+ }
+
+
+
+ private static void ReverseRevisions(WordprocessingDocument doc)
+ {
+ ReverseRevisionsForPart(doc.MainDocumentPart);
+ foreach (var part in doc.MainDocumentPart.HeaderParts)
+ ReverseRevisionsForPart(part);
+ foreach (var part in doc.MainDocumentPart.FooterParts)
+ ReverseRevisionsForPart(part);
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ ReverseRevisionsForPart(doc.MainDocumentPart.EndnotesPart);
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ ReverseRevisionsForPart(doc.MainDocumentPart.FootnotesPart);
+ }
+
+ private static void ReverseRevisionsForPart(OpenXmlPart part)
+ {
+ var xDoc = part.GetXDocument();
+ ReverseRevisionsInfo rri = new ReverseRevisionsInfo();
+ rri.InInsert = false;
+ var newRoot = (XElement)ReverseRevisionsTransform(xDoc.Root, rri);
+ newRoot = (XElement)RemoveRsidTransform(newRoot);
+ xDoc.Root.ReplaceWith(newRoot);
+ part.PutXDocument();
+ }
+
+ private static object RemoveRsidTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.rsid)
+ return null;
+ return new XElement(element.Name,
+ element.Attributes().Where(a => a.Name != W.rsid &&
+ a.Name != W.rsidDel &&
+ a.Name != W.rsidP &&
+ a.Name != W.rsidR &&
+ a.Name != W.rsidRDefault &&
+ a.Name != W.rsidRPr &&
+ a.Name != W.rsidSect &&
+ a.Name != W.rsidTr),
+ element.Nodes().Select(n => RemoveRsidTransform(n)));
+ }
+ return node;
+ }
+
+ private static object MergeAdjacentTablesTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Element(W.tbl) != null)
+ {
+ var grouped = element
+ .Elements()
+ .GroupAdjacent(e =>
+ {
+ if (e.Name != W.tbl)
+ return "";
+ var bidiVisual = e.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
+ var bidiVisString = bidiVisual == null ? "" : "|bidiVisual";
+ var key = "tbl" + bidiVisString;
+ return key;
+ });
+
+ var newContent = grouped
+ .Select(g =>
+ {
+ if (g.Key == "" || g.Count() == 1)
+ return (object)g;
+ var rolled = g
+ .Select(tbl =>
+ {
+ var gridCols = tbl
+ .Elements(W.tblGrid)
+ .Elements(W.gridCol)
+ .Attributes(W._w)
+ .Select(a => (int)a)
+ .Rollup(0, (s, i) => s + i);
+ return gridCols;
+ })
+ .SelectMany(m => m)
+ .Distinct()
+ .OrderBy(w => w)
+ .ToArray();
+ var newTable = new XElement(W.tbl,
+ g.First().Elements(W.tblPr),
+ new XElement(W.tblGrid,
+ rolled.Select((r, i) =>
+ {
+ int v;
+ if (i == 0)
+ v = r;
+ else
+ v = r - rolled[i - 1];
+ return new XElement(W.gridCol,
+ new XAttribute(W._w, v));
+ })),
+ g.Select(tbl =>
+ {
+ var fixedWidthsTbl = FixWidths(tbl);
+ var newRows = fixedWidthsTbl.Elements(W.tr)
+ .Select(tr =>
+ {
+ XElement newRow = new XElement(W.tr,
+ tr.Attributes(),
+ tr.Elements().Where(e => e.Name != W.tc),
+ tr.Elements(W.tc).Select(tc =>
+ {
+ int? w = (int?)tc
+ .Elements(W.tcPr)
+ .Elements(W.tcW)
+ .Attributes(W._w)
+ .FirstOrDefault();
+ if (w == null)
+ return tc;
+ var cellsToLeft = tc
+ .Parent
+ .Elements(W.tc)
+ .TakeWhile(btc => btc != tc);
+ int widthToLeft = 0;
+ if (cellsToLeft.Any())
+ widthToLeft = cellsToLeft
+ .Elements(W.tcPr)
+ .Elements(W.tcW)
+ .Attributes(W._w)
+ .Select(wi => (int)wi)
+ .Sum();
+ var rolledPairs = new[] { new
+ {
+ GridValue = 0,
+ Index = 0,
+ }}
+ .Concat(
+ rolled
+ .Select((r, i) => new
+ {
+ GridValue = r,
+ Index = i + 1,
+ }));
+ var start = rolledPairs
+ .FirstOrDefault(t => t.GridValue >= widthToLeft);
+ if (start != null)
+ {
+ var gridsRequired = rolledPairs
+ .Skip(start.Index)
+ .TakeWhile(rp => rp.GridValue - start.GridValue < w)
+ .Count();
+ var tcPr = new XElement(W.tcPr,
+ tc.Elements(W.tcPr).Elements().Where(e => e.Name != W.gridSpan),
+ gridsRequired != 1 ?
+ new XElement(W.gridSpan,
+ new XAttribute(W.val, gridsRequired)) :
+ null);
+ var orderedTcPr = new XElement(W.tcPr,
+ tcPr.Elements().OrderBy(e =>
+ {
+ if (Order_tcPr.ContainsKey(e.Name))
+ return Order_tcPr[e.Name];
+ return 999;
+ }));
+ var newCell = new XElement(W.tc,
+ orderedTcPr,
+ tc.Elements().Where(e => e.Name != W.tcPr));
+ return newCell;
+ }
+ return tc;
+ }));
+ return newRow;
+ });
+ return newRows;
+ }));
+ return newTable;
+ });
+ return new XElement(element.Name,
+ element.Attributes(),
+ newContent);
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => MergeAdjacentTablesTransform(n)));
+ }
+ return node;
+ }
+
+ private static object ReverseRevisionsTransform(XNode node, ReverseRevisionsInfo rri)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ var parent = element
+ .Ancestors()
+ .Where(a => a.Name != W.sdtContent && a.Name != W.sdt && a.Name != W.smartTag)
+ .FirstOrDefault();
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Deleted run
+#if false
+ <w:p>
+ <w:r>
+ <w:t xml:space="preserve">Video </w:t>
+ </w:r>
+ <w:del>
+ <w:r>
+ <w:delText xml:space="preserve">provides </w:delText>
+ </w:r>
+ </w:del>
+ <w:r>
+ <w:t>a powerful way to help you prove your point.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.del &&
+ parent.Name == W.p)
+ {
+ return new XElement(W.ins,
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Deleted paragraph mark
+#if false
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:del w:id="0" w:author="Eric White" w:date="2017-03-24T21:52:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:r>
+ <w:t>Video provides a powerful way to help you prove your point.</w:t>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:r>
+ <w:t>You can also type a keyword to search online for the video that best fits your document.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.del &&
+ parent.Name == W.rPr &&
+ parent.Parent.Name == W.pPr)
+ {
+ return new XElement(W.ins);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Inserted paragraph mark
+#if false
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:ins w:id="0" w:author="Eric White" w:date="2017-03-24T21:58:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:r>
+ <w:t xml:space="preserve">Video provides a powerful way to help you prove your point. </w:t>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:r>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>When you click Online Video, you can paste in the embed code for the video you want to add.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.ins &&
+ parent.Name == W.rPr &&
+ parent.Parent.Name == W.pPr)
+ {
+ return new XElement(W.del);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Inserted run
+#if false
+ <w:p>
+ <w:r>
+ <w:t xml:space="preserve">Video </w:t>
+ </w:r>
+ <w:ins>
+ <w:r>
+ <w:t xml:space="preserve">provides </w:t>
+ </w:r>
+ </w:ins>
+ <w:r>
+ <w:t>a powerful way to help you prove your point.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.ins &&
+ parent.Name == W.p)
+ {
+ var newRri = new ReverseRevisionsInfo() { InInsert = true };
+ return new XElement(W.del,
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Deleted table row
+#if false
+ <w:tbl>
+ <w:tr>
+ <w:tc>
+ <w:p>
+ <w:r>
+ <w:t>1</w:t>
+ </w:r>
+ </w:p>
+ </w:tc>
+ </w:tr>
+ <w:tr>
+ <w:trPr>
+ <w:del w:id="0" w:author="Eric White" w:date="2017-03-24T22:15:00Z"/>
+ </w:trPr>
+ <w:tc>
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:del w:id="1" w:author="Eric White" w:date="2017-03-24T22:15:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:del w:id="2" w:author="Eric White" w:date="2017-03-24T22:15:00Z">
+ <w:r>
+ <w:delText>4</w:delText>
+ </w:r>
+ </w:del>
+ </w:p>
+ </w:tc>
+ </w:tr>
+ <w:tr>
+ <w:tc>
+ <w:p>
+ <w:r>
+ <w:t>7</w:t>
+ </w:r>
+ </w:p>
+ </w:tc>
+ </w:tr>
+ </w:tbl>
+#endif
+ if (element.Name == W.del &&
+ parent.Name == W.trPr)
+ {
+ return new XElement(W.ins);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Inserted table row
+#if false
+ <w:tbl>
+ <w:tr>
+ <w:tc>
+ <w:p>
+ <w:r>
+ <w:t>1</w:t>
+ </w:r>
+ </w:p>
+ </w:tc>
+ </w:tr>
+ <w:tr>
+ <w:trPr>
+ <w:ins w:id="0" w:author="Eric White" w:date="2017-03-24T22:16:00Z"/>
+ </w:trPr>
+ <w:tc>
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:ins w:id="1" w:author="Eric White" w:date="2017-03-24T22:16:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:ins w:id="2" w:author="Eric White" w:date="2017-03-24T22:16:00Z">
+ <w:r>
+ <w:t>1a</w:t>
+ </w:r>
+ </w:ins>
+ </w:p>
+ </w:tc>
+ </w:tr>
+ <w:tr>
+ <w:tc>
+ <w:p>
+ <w:r>
+ <w:t>4</w:t>
+ </w:r>
+ </w:p>
+ </w:tc>
+ </w:tr>
+ </w:tbl>
+#endif
+ if (element.Name == W.ins &&
+ parent.Name == W.trPr)
+ {
+ return new XElement(W.del);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Deleted math control character
+#if false
+ <w:p w:rsidR="007F4E48" w:rsidRDefault="00C9403B">
+ <m:oMathPara>
+ <m:oMath>
+ <m:r>
+ <w:rPr>
+ <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
+ </w:rPr>
+ <m:t>A=</m:t>
+ </m:r>
+ <m:r>
+ <w:del w:id="0" w:author="Eric White" w:date="2017-03-24T22:53:00Z">
+ <w:rPr>
+ <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
+ </w:rPr>
+ <m:t>2</m:t>
+ </w:del>
+ </m:r>
+ <m:r>
+ <w:rPr>
+ <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
+ </w:rPr>
+ <m:t>π</m:t>
+ </m:r>
+#endif
+ if (element.Name == W.del &&
+ parent.Name == M.r)
+ {
+ return new XElement(W.ins,
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Inserted math control character
+#if false
+ <w:p w:rsidR="007F4E48" w:rsidRDefault="00C9403B">
+ <m:oMathPara>
+ <m:oMath>
+ <m:r>
+ <w:rPr>
+ <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
+ </w:rPr>
+ <m:t>A=</m:t>
+ </m:r>
+ <m:r>
+ <w:ins w:id="0" w:author="Eric White" w:date="2017-03-24T22:54:00Z">
+ <w:rPr>
+ <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
+ </w:rPr>
+ <m:t>2</m:t>
+ </w:ins>
+ </m:r>
+ <m:r>
+ <w:rPr>
+ <w:rFonts w:ascii="Cambria Math" w:hAnsi="Cambria Math"/>
+ </w:rPr>
+ <m:t>π</m:t>
+ </m:r>
+#endif
+ if (element.Name == W.ins &&
+ parent.Name == M.r)
+ {
+ return new XElement(W.del,
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // moveFrom / moveTo
+#if false
+ <w:p>
+ <w:r>
+ <w:t>Video provides a powerful way.</w:t>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:moveFrom w:id="0" w:author="Eric White" w:date="2017-03-24T23:18:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:moveFromRangeStart w:id="1" w:author="Eric White" w:date="2017-03-24T23:18:00Z" w:name="move478160808"/>
+ <w:moveFrom w:id="2" w:author="Eric White" w:date="2017-03-24T23:18:00Z">
+ <w:r>
+ <w:t>When you click Online Video.</w:t>
+ </w:r>
+ </w:moveFrom>
+ </w:p>
+ <w:moveFromRangeEnd w:id="1"/>
+ <w:p>
+ <w:r>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>You can also type a keyword.</w:t>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:moveTo w:id="3" w:author="Eric White" w:date="2017-03-24T23:18:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:moveToRangeStart w:id="5" w:author="Eric White" w:date="2017-03-24T23:18:00Z" w:name="move478160808"/>
+ <w:moveTo w:id="6" w:author="Eric White" w:date="2017-03-24T23:18:00Z">
+ <w:r>
+ <w:t>When you click Online Video.</w:t>
+ </w:r>
+ </w:moveTo>
+ </w:p>
+ <w:moveToRangeEnd w:id="5"/>
+ <w:p>
+ <w:r>
+ <w:t>Make your document look professionally produced.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.moveFrom)
+ {
+ return new XElement(W.moveTo,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.moveFromRangeStart)
+ {
+ return new XElement(W.moveToRangeStart,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.moveFromRangeEnd)
+ {
+ return new XElement(W.moveToRangeEnd,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.moveTo)
+ {
+ return new XElement(W.moveFrom,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.moveToRangeStart)
+ {
+ return new XElement(W.moveFromRangeStart,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.moveToRangeEnd)
+ {
+ return new XElement(W.moveFromRangeEnd,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Deleted content control
+#if false
+ <w:p>
+ <w:customXmlDelRangeStart w:id="1" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
+ <w:sdt>
+ <w:sdtPr>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:id w:val="990292373"/>
+ <w:placeholder>
+ <w:docPart w:val="DefaultPlaceholder_-1854013440"/>
+ </w:placeholder>
+ <w:text/>
+ </w:sdtPr>
+ <w:sdtContent>
+ <w:customXmlDelRangeEnd w:id="1"/>
+ <w:r>
+ <w:t>Video</w:t>
+ </w:r>
+ <w:customXmlDelRangeStart w:id="2" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
+ </w:sdtContent>
+ </w:sdt>
+ <w:customXmlDelRangeEnd w:id="2"/>
+ <w:r>
+ <w:t xml:space="preserve"> provides a powerful way to help you prove your point.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.customXmlDelRangeStart)
+ {
+ return new XElement(W.customXmlInsRangeStart,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.customXmlDelRangeEnd)
+ {
+ return new XElement(W.customXmlInsRangeEnd,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Inserted content control
+#if false
+ <w:p>
+ <w:customXmlInsRangeStart w:id="0" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
+ <w:sdt>
+ <w:sdtPr>
+ <w:id w:val="-473839966"/>
+ <w:placeholder>
+ <w:docPart w:val="DefaultPlaceholder_-1854013440"/>
+ </w:placeholder>
+ <w:text/>
+ </w:sdtPr>
+ <w:sdtContent>
+ <w:customXmlInsRangeEnd w:id="0"/>
+ <w:r>
+ <w:t>Video</w:t>
+ </w:r>
+ <w:customXmlInsRangeStart w:id="1" w:author="Eric White" w:date="2017-03-25T22:10:00Z"/>
+ </w:sdtContent>
+ </w:sdt>
+ <w:customXmlInsRangeEnd w:id="1"/>
+ <w:r>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t xml:space="preserve"> provides a powerful way to help you prove your point.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.customXmlInsRangeStart)
+ {
+ return new XElement(W.customXmlDelRangeStart,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.customXmlInsRangeEnd)
+ {
+ return new XElement(W.customXmlDelRangeEnd,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Moved content control
+#if false
+ <w:p>
+ <w:r>
+ <w:t>Video provides a powerful way.</w:t>
+ </w:r>
+ </w:p>
+ <w:customXmlMoveFromRangeStart w:id="0" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
+ <w:moveFromRangeStart w:id="1" w:author="Eric White" w:date="2017-03-25T22:21:00Z" w:name="move478243824" w:displacedByCustomXml="next"/>
+ <w:sdt>
+ <w:sdtPr>
+ <w:id w:val="-2060007328"/>
+ <w:placeholder>
+ <w:docPart w:val="DefaultPlaceholder_-1854013440"/>
+ </w:placeholder>
+ </w:sdtPr>
+ <w:sdtContent>
+ <w:customXmlMoveFromRangeEnd w:id="0"/>
+ <w:p w:rsidR="00D306FD" w:rsidDel="001037E6" w:rsidRDefault="00D306FD">
+ <w:pPr>
+ <w:rPr>
+ <w:moveFrom w:id="2" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ </w:pPr>
+ <w:moveFrom w:id="3" w:author="Eric White" w:date="2017-03-25T22:21:00Z">
+ <w:r w:rsidDel="001037E6">
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>When you click Online Video.</w:t>
+ </w:r>
+ </w:moveFrom>
+ </w:p>
+ <w:customXmlMoveFromRangeStart w:id="4" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
+ </w:sdtContent>
+ </w:sdt>
+ <w:customXmlMoveFromRangeEnd w:id="4"/>
+ <w:moveFromRangeEnd w:id="1"/>
+ <w:p>
+ <w:r>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>You can also type a keyword.</w:t>
+ </w:r>
+ </w:p>
+ <w:p>
+ <w:r>
+ <w:rPr>
+ <w:lang w:val="en-US"/>
+ </w:rPr>
+ <w:t>To make your document look.</w:t>
+ </w:r>
+ </w:p>
+ <w:customXmlMoveToRangeStart w:id="5" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
+ <w:moveToRangeStart w:id="6" w:author="Eric White" w:date="2017-03-25T22:21:00Z" w:name="move478243824" w:displacedByCustomXml="next"/>
+ <w:sdt>
+ <w:sdtPr>
+ <w:id w:val="-483622649"/>
+ <w:placeholder>
+ <w:docPart w:val="DC46F197491D4EC8B79DB4CE2D22E222"/>
+ </w:placeholder>
+ </w:sdtPr>
+ <w:sdtContent>
+ <w:customXmlMoveToRangeEnd w:id="5"/>
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:moveTo w:id="8" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:moveTo w:id="9" w:author="Eric White" w:date="2017-03-25T22:21:00Z">
+ <w:r>
+ <w:t>When you click Online Video.</w:t>
+ </w:r>
+ </w:moveTo>
+ </w:p>
+ <w:customXmlMoveToRangeStart w:id="10" w:author="Eric White" w:date="2017-03-25T22:21:00Z"/>
+ </w:sdtContent>
+ </w:sdt>
+ <w:customXmlMoveToRangeEnd w:id="10"/>
+ <w:moveToRangeEnd w:id="6"/>
+ <w:p>
+ <w:ins w:id="11" w:author="Eric White" w:date="2017-03-25T22:21:00Z">
+ <w:r>
+ <w:t xml:space="preserve"> </w:t>
+ </w:r>
+ </w:ins>
+ <w:r>
+ <w:t>For example, you can add.</w:t>
+ </w:r>
+ </w:p>
+#endif
+ if (element.Name == W.customXmlMoveFromRangeStart)
+ {
+ return new XElement(W.customXmlMoveToRangeStart,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.customXmlMoveFromRangeEnd)
+ {
+ return new XElement(W.customXmlMoveToRangeEnd,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.customXmlMoveToRangeStart)
+ {
+ return new XElement(W.customXmlMoveFromRangeStart,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ if (element.Name == W.customXmlMoveToRangeEnd)
+ {
+ return new XElement(W.customXmlMoveFromRangeEnd,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Deleted field code
+#if false
+ <w:p>
+ <w:pPr>
+ <w:rPr>
+ <w:del w:id="0" w:author="Eric White" w:date="2017-03-25T22:43:00Z"/>
+ </w:rPr>
+ </w:pPr>
+ <w:del w:id="1" w:author="Eric White" w:date="2017-03-25T22:43:00Z">
+ <w:r>
+ <w:fldChar w:fldCharType="begin"/>
+ </w:r>
+ <w:r>
+ <w:delInstrText xml:space="preserve"> D</w:delInstrText>
+ </w:r>
+ <w:r>
+ <w:rPr>
+ <w:color w:val="FF0000"/>
+ </w:rPr>
+ <w:delInstrText>A</w:delInstrText>
+ </w:r>
+ <w:r>
+ <w:delInstrText xml:space="preserve">TE </w:delInstrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="separate"/>
+ </w:r>
+ <w:r>
+ <w:delText>25/03/2017</w:delText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="end"/>
+ </w:r>
+ </w:del>
+ </w:p>
+#endif
+ if (element.Name == W.delInstrText)
+ {
+ return new XElement(W.instrText,
+ element.Attributes(), // pulls in xml:space attribute
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Change inserted instrText element to w:delInstrText
+ if (element.Name == W.instrText && rri.InInsert)
+ {
+ return new XElement(W.delInstrText,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Change inserted text element to w:delText
+ if (element.Name == W.t && rri.InInsert)
+ {
+ return new XElement(W.delText,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Change w:delText to w:t
+ if (element.Name == W.delText)
+ {
+ return new XElement(W.t,
+ element.Attributes(), // pulls in xml:space attribute
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Identity transform
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => ReverseRevisionsTransform(n, rri)));
+ }
+ return node;
+ }
+
+ public static WmlDocument AcceptRevisions(WmlDocument document)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ AcceptRevisions(doc);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void AcceptRevisions(WordprocessingDocument doc)
+ {
+ AcceptRevisionsForPart(doc.MainDocumentPart);
+ foreach (var part in doc.MainDocumentPart.HeaderParts)
+ AcceptRevisionsForPart(part);
+ foreach (var part in doc.MainDocumentPart.FooterParts)
+ AcceptRevisionsForPart(part);
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ AcceptRevisionsForPart(doc.MainDocumentPart.EndnotesPart);
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ AcceptRevisionsForPart(doc.MainDocumentPart.FootnotesPart);
+ if (doc.MainDocumentPart.StyleDefinitionsPart != null)
+ AcceptRevisionsForStylesDefinitionPart(doc.MainDocumentPart.StyleDefinitionsPart);
+ }
+
+ private static void AcceptRevisionsForStylesDefinitionPart(StyleDefinitionsPart stylesDefinitionsPart)
+ {
+ var xDoc = stylesDefinitionsPart.GetXDocument();
+ var newRoot = AcceptRevisionsForStylesTransform(xDoc.Root);
+ xDoc.Root.ReplaceWith(newRoot);
+ stylesDefinitionsPart.PutXDocument();
+ }
+
+ private static object AcceptRevisionsForStylesTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.pPrChange || element.Name == W.rPrChange)
+ return null;
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptRevisionsForStylesTransform(n)));
+ }
+ return node;
+ }
+
+ public static void AcceptRevisionsForPart(OpenXmlPart part)
+ {
+ XElement documentElement = part.GetXDocument().Root;
+ documentElement = (XElement)RemoveRsidTransform(documentElement);
+ documentElement = (XElement)FixUpDeletedOrInsertedFieldCodesTransform(documentElement);
+ var containsMoveFromMoveTo = documentElement.Descendants(W.moveFrom).Any();
+ documentElement = (XElement)AcceptMoveFromMoveToTransform(documentElement);
+ documentElement = AcceptMoveFromRanges(documentElement);
+ // AcceptParagraphEndTagsInMoveFromTransform needs rewritten similar to AcceptDeletedAndMoveFromParagraphMarks
+ documentElement = (XElement)AcceptParagraphEndTagsInMoveFromTransform(documentElement);
+ documentElement = AcceptDeletedAndMovedFromContentControls(documentElement);
+ documentElement = AcceptDeletedAndMoveFromParagraphMarks(documentElement);
+ if (containsMoveFromMoveTo)
+ documentElement = (XElement)RemoveRowsLeftEmptyByMoveFrom(documentElement);
+ documentElement = (XElement)AcceptAllOtherRevisionsTransform(documentElement);
+ documentElement = (XElement)AcceptDeletedCellsTransform(documentElement);
+ documentElement = (XElement)MergeAdjacentTablesTransform(documentElement);
+ documentElement = (XElement)AddEmptyParagraphToAnyEmptyCells(documentElement);
+ documentElement.Descendants().Attributes().Where(a => a.Name == PT.UniqueId || a.Name == PT.RunIds).Remove();
+ documentElement.Descendants(W.numPr).Where(np => !np.HasElements).Remove();
+ XDocument newXDoc = new XDocument(documentElement);
+ part.PutXDocument(newXDoc);
+ }
+
+ // Note that AcceptRevisionsForElement is an incomplete implementation. It is not possible to accept all varieties of revisions
+ // for a single paragraph. The paragraph may contain a marker for a deleted or inserted content control, as one example, of
+ // which there are many. This method accepts simple revisions, such as deleted or inserted text, which is the most common use
+ // case.
+ public static XElement AcceptRevisionsForElement(XElement element)
+ {
+ XElement rElement = element;
+ rElement = (XElement)RemoveRsidTransform(rElement);
+ var containsMoveFromMoveTo = rElement.Descendants(W.moveFrom).Any();
+ rElement = (XElement)AcceptMoveFromMoveToTransform(rElement);
+ rElement = (XElement)AcceptAllOtherRevisionsTransform(rElement);
+ rElement.Descendants().Attributes().Where(a => a.Name == PT.UniqueId || a.Name == PT.RunIds).Remove();
+ rElement.Descendants(W.numPr).Where(np => !np.HasElements).Remove();
+ return rElement;
+ }
+
+ private static object FixUpDeletedOrInsertedFieldCodesTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ {
+ // 1 other
+ // 2 w:del/w:r/w:fldChar
+ // 3 w:ins/w:r/w:fldChar
+ // 4 w:instrText
+
+ // formulate new paragraph, looking for 4 that has 2 (or 3) before and after. Then put in a w:del (or w:ins), transforming w:instrText to w:delInstrText if w:del.
+ // transform 1, 2, 3 as usual
+
+ var groupedParaContentsKey = element.Elements().Select(e =>
+ {
+ if (e.Name == W.del && e.Elements(W.r).Elements(W.fldChar).Any())
+ return 2;
+ if (e.Name == W.ins && e.Elements(W.r).Elements(W.fldChar).Any())
+ return 3;
+ if (e.Name == W.r && e.Element(W.instrText) != null)
+ return 4;
+ return 1;
+ });
+
+ var zipped = element.Elements().Zip(groupedParaContentsKey, (e, k) => new { Ele = e, Key = k });
+
+ var grouped = zipped.GroupAdjacent(z => z.Key).ToArray();
+
+ var gLen = grouped.Length;
+
+ //if (gLen != 1)
+ // Console.WriteLine();
+
+ var newParaContents = grouped
+ .Select((g, i) =>
+ {
+ if (g.Key == 1 || g.Key == 2 || g.Key == 3)
+ return (object)g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele));
+ if (g.Key == 4)
+ {
+ if (i == 0 || i == gLen - 1)
+ return g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele));
+ if (grouped[i-1].Key == 2 &&
+ grouped[i+1].Key == 2)
+ {
+ return new XElement(W.del,
+ g.Select(gc => TransformInstrTextToDelInstrText(gc.Ele)));
+ }
+ else if (grouped[i - 1].Key == 3 &&
+ grouped[i + 1].Key == 3)
+ {
+ return new XElement(W.ins,
+ g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele)));
+ }
+ else
+ {
+ return g.Select(gc => FixUpDeletedOrInsertedFieldCodesTransform(gc.Ele));
+ }
+ }
+ throw new OpenXmlPowerToolsException("Internal error");
+ });
+
+ var newParagraph = new XElement(W.p,
+ element.Attributes(),
+ newParaContents);
+ return newParagraph;
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => FixUpDeletedOrInsertedFieldCodesTransform(n)));
+ }
+ return node;
+ }
+
+ private static object TransformInstrTextToDelInstrText(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.instrText)
+ return new XElement(W.delInstrText,
+ element.Attributes(),
+ element.Nodes());
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformInstrTextToDelInstrText(n)));
+ }
+ return node;
+ }
+
+ private static object AddEmptyParagraphToAnyEmptyCells(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.tc && !element.Elements().Where(e => e.Name != W.tcPr).Any())
+ return new XElement(W.tc,
+ element.Attributes(),
+ element.Elements(),
+ new XElement(W.p));
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AddEmptyParagraphToAnyEmptyCells(n)));
+ }
+ return node;
+ }
+
+ private static Dictionary<XName, int> Order_tcPr = new Dictionary<XName, int>
+ {
+ { W.cnfStyle, 10 },
+ { W.tcW, 20 },
+ { W.gridSpan, 30 },
+ { W.hMerge, 40 },
+ { W.vMerge, 50 },
+ { W.tcBorders, 60 },
+ { W.shd, 70 },
+ { W.noWrap, 80 },
+ { W.tcMar, 90 },
+ { W.textDirection, 100 },
+ { W.tcFitText, 110 },
+ { W.vAlign, 120 },
+ { W.hideMark, 130 },
+ { W.headers, 140 },
+ };
+
+ private static XElement FixWidths(XElement tbl)
+ {
+ var newTbl = new XElement(tbl);
+ var gridLines = tbl.Elements(W.tblGrid).Elements(W.gridCol).Attributes(W._w).Select(w => (int)w).ToArray();
+ foreach (var tr in newTbl.Elements(W.tr))
+ {
+ int used = 0;
+ int lastUsed = -1;
+ foreach (var tc in tr.Elements(W.tc))
+ {
+ var tcW = tc.Elements(W.tcPr).Elements(W.tcW).Attributes(W._w).FirstOrDefault();
+ if (tcW != null)
+ {
+ int? gridSpan = (int?)tc.Elements(W.tcPr).Elements(W.gridSpan).Attributes(W.val).FirstOrDefault();
+
+ if (gridSpan == null)
+ gridSpan = 1;
+
+ var z = Math.Min(gridLines.Length - 1, lastUsed + (int)gridSpan);
+ int w = gridLines.Where((g, i) => i > lastUsed && i <= z).Sum();
+ tcW.Value = w.ToString();
+
+ lastUsed += (int)gridSpan;
+ used += (int)gridSpan;
+ }
+ }
+ }
+ return newTbl;
+ }
+
+ private static object AcceptMoveFromMoveToTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.moveTo)
+ return element.Nodes().Select(n => AcceptMoveFromMoveToTransform(n));
+ if (element.Name == W.moveFrom)
+ return null;
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptMoveFromMoveToTransform(n)));
+ }
+ return node;
+ }
+
+ private static XElement AcceptMoveFromRanges(XElement document)
+ {
+ string wordProcessingNamespacePrefix = document.GetPrefixOfNamespace(W.w);
+
+ // The following lists contain the elements that are between start/end elements.
+ List<XElement> startElementTagsInMoveFromRange = new List<XElement>();
+ List<XElement> endElementTagsInMoveFromRange = new List<XElement>();
+
+ // Following are the elements that *may* be in a range that has both start and end
+ // elements.
+ Dictionary<string, PotentialInRangeElements> potentialDeletedElements =
+ new Dictionary<string, PotentialInRangeElements>();
+
+ foreach (var tag in DescendantAndSelfTags(document))
+ {
+ if (tag.Element.Name == W.moveFromRangeStart)
+ {
+ string id = tag.Element.Attribute(W.id).Value;
+ potentialDeletedElements.Add(id, new PotentialInRangeElements());
+ continue;
+ }
+ if (tag.Element.Name == W.moveFromRangeEnd)
+ {
+ string id = tag.Element.Attribute(W.id).Value;
+ if (potentialDeletedElements.ContainsKey(id))
+ {
+ startElementTagsInMoveFromRange.AddRange(
+ potentialDeletedElements[id].PotentialStartElementTagsInRange);
+ endElementTagsInMoveFromRange.AddRange(
+ potentialDeletedElements[id].PotentialEndElementTagsInRange);
+ potentialDeletedElements.Remove(id);
+ }
+ continue;
+ }
+ if (potentialDeletedElements.Count > 0)
+ {
+ if (tag.TagType == TagTypeEnum.Element &&
+ (tag.Element.Name != W.moveFromRangeStart &&
+ tag.Element.Name != W.moveFromRangeEnd))
+ {
+ foreach (var id in potentialDeletedElements)
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ continue;
+ }
+ if (tag.TagType == TagTypeEnum.EmptyElement &&
+ (tag.Element.Name != W.moveFromRangeStart &&
+ tag.Element.Name != W.moveFromRangeEnd))
+ {
+ foreach (var id in potentialDeletedElements)
+ {
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ }
+ continue;
+ }
+ if (tag.TagType == TagTypeEnum.EndElement &&
+ (tag.Element.Name != W.moveFromRangeStart &&
+ tag.Element.Name != W.moveFromRangeEnd))
+ {
+ foreach (var id in potentialDeletedElements)
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ continue;
+ }
+ }
+ }
+ var moveFromElementsToDelete = startElementTagsInMoveFromRange
+ .Intersect(endElementTagsInMoveFromRange)
+ .ToArray();
+ if (moveFromElementsToDelete.Count() > 0)
+ return (XElement)AcceptMoveFromRangesTransform(
+ document, moveFromElementsToDelete);
+ return document;
+ }
+
+ private enum MoveFromCollectionType
+ {
+ ParagraphEndTagInMoveFromRange,
+ Other
+ };
+
+ private static object AcceptParagraphEndTagsInMoveFromTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (W.BlockLevelContentContainers.Contains(element.Name))
+ {
+ var groupedBodyChildren = element
+ .Elements()
+ .GroupAdjacent(c =>
+ {
+ BlockContentInfo pi = c.GetParagraphInfo();
+ if (pi.ThisBlockContentElement != null)
+ {
+ bool paragraphMarkIsInMoveFromRange =
+ pi.ThisBlockContentElement.Elements(W.moveFromRangeStart).Any() &&
+ !pi.ThisBlockContentElement.Elements(W.moveFromRangeEnd).Any();
+ if (paragraphMarkIsInMoveFromRange)
+ return MoveFromCollectionType.ParagraphEndTagInMoveFromRange;
+ }
+ XElement previousContentElement = c.ContentElementsBeforeSelf()
+ .Where(e => e.GetParagraphInfo().ThisBlockContentElement != null)
+ .FirstOrDefault();
+ if (previousContentElement != null)
+ {
+ BlockContentInfo pi2 = previousContentElement.GetParagraphInfo();
+ if (c.Name == W.p &&
+ pi2.ThisBlockContentElement.Elements(W.moveFromRangeStart).Any() &&
+ !pi2.ThisBlockContentElement.Elements(W.moveFromRangeEnd).Any())
+ return MoveFromCollectionType.ParagraphEndTagInMoveFromRange;
+ }
+ return MoveFromCollectionType.Other;
+ })
+ .ToList();
+
+ // If there is only one group, and it's key is MoveFromCollectionType.Other
+ // then there is nothing to do.
+ if (groupedBodyChildren.Count() == 1 &&
+ groupedBodyChildren.First().Key == MoveFromCollectionType.Other)
+ {
+ XElement newElement = new XElement(element.Name,
+ element.Attributes(),
+ groupedBodyChildren.Select(g =>
+ {
+ if (g.Key == MoveFromCollectionType.Other)
+ return (object)g;
+
+ // This is a transform that produces the first element in the
+ // collection, except that the paragraph in the descendents is
+ // replaced with a new paragraph that contains all contents of the
+ // existing paragraph, plus subsequent elements in the group
+ // collection, where the paragraph in each of those groups is
+ // collapsed.
+ return CoalesqueParagraphEndTagsInMoveFromTransform(g.First(), g);
+ }));
+ return newElement;
+ }
+ else
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n =>
+ AcceptParagraphEndTagsInMoveFromTransform(n)));
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptParagraphEndTagsInMoveFromTransform(n)));
+ }
+ return node;
+ }
+
+ private static object AcceptAllOtherRevisionsTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ /// Accept inserted text, inserted paragraph marks, etc.
+ /// Collapse all w:ins elements.
+
+ if (element.Name == W.ins)
+ return element
+ .Nodes()
+ .Select(n => AcceptAllOtherRevisionsTransform(n));
+
+ /// Remove all of the following elements. These elements are processed in:
+ /// AcceptDeletedAndMovedFromContentControls
+ /// AcceptMoveFromMoveToTransform
+ /// AcceptDeletedAndMoveFromParagraphMarksTransform
+ /// AcceptParagraphEndTagsInMoveFromTransform
+ /// AcceptMoveFromRanges
+
+ if (element.Name == W.customXmlDelRangeStart ||
+ element.Name == W.customXmlDelRangeEnd ||
+ element.Name == W.customXmlInsRangeStart ||
+ element.Name == W.customXmlInsRangeEnd ||
+ element.Name == W.customXmlMoveFromRangeStart ||
+ element.Name == W.customXmlMoveFromRangeEnd ||
+ element.Name == W.customXmlMoveToRangeStart ||
+ element.Name == W.customXmlMoveToRangeEnd ||
+ element.Name == W.moveFromRangeStart ||
+ element.Name == W.moveFromRangeEnd ||
+ element.Name == W.moveToRangeStart ||
+ element.Name == W.moveToRangeEnd)
+ return null;
+
+ /// Accept revisions in formatting on paragraphs.
+ /// Accept revisions in formatting on runs.
+ /// Accept revisions for applied styles to a table.
+ /// Accept revisions for grid revisions to a table.
+ /// Accept revisions for column properties.
+ /// Accept revisions for row properties.
+ /// Accept revisions for table level property exceptions.
+ /// Accept revisions for section properties.
+ /// Accept numbering revision in fields.
+ /// Accept deleted field code text.
+ /// Accept deleted literal text.
+ /// Accept inserted cell.
+
+ if (element.Name == W.pPrChange ||
+ element.Name == W.rPrChange ||
+ element.Name == W.tblPrChange ||
+ element.Name == W.tblGridChange ||
+ element.Name == W.tcPrChange ||
+ element.Name == W.trPrChange ||
+ element.Name == W.tblPrExChange ||
+ element.Name == W.sectPrChange ||
+ element.Name == W.numberingChange ||
+ element.Name == W.delInstrText ||
+ element.Name == W.delText ||
+ element.Name == W.cellIns)
+ return null;
+
+ // Accept revisions for deleted math control character.
+ // Match m:f/m:fPr/m:ctrlPr/w:del, remove m:f.
+
+ if (element.Name == M.f &&
+ element.Elements(M.fPr).Elements(M.ctrlPr).Elements(W.del).Any())
+ return null;
+
+ // Accept revisions for deleted rows in tables.
+ // Match w:tr/w:trPr/w:del, remove w:tr.
+
+ if (element.Name == W.tr &&
+ element.Elements(W.trPr).Elements(W.del).Any())
+ return null;
+
+ // Accept deleted text in paragraphs.
+
+ if (element.Name == W.del)
+ return null;
+
+ // Accept revisions for vertically merged cells.
+ // cellMerge with a parent of tcPr, with attribute w:vMerge="rest" transformed
+ // to <w:vMerge w:val="restart"/>
+ // cellMerge with a parent of tcPr, with attribute w:vMerge="cont" transformed
+ // to <w:vMerge w:val="continue"/>
+
+ if (element.Name == W.cellMerge &&
+ element.Parent.Name == W.tcPr &&
+ (string)element.Attribute(W.vMerge) == "rest")
+ return new XElement(W.vMerge,
+ new XAttribute(W.val, "restart"));
+ if (element.Name == W.cellMerge &&
+ element.Parent.Name == W.tcPr &&
+ (string)element.Attribute(W.vMerge) == "cont")
+ return new XElement(W.vMerge,
+ new XAttribute(W.val, "continue"));
+
+ // Otherwise do identity clone.
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptAllOtherRevisionsTransform(n)));
+ }
+ return node;
+ }
+
+ private static object CollapseParagraphTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ return element.Elements().Where(e => e.Name != W.pPr);
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => CollapseParagraphTransform(n)));
+ }
+ return node;
+ }
+
+ private enum DeletedParagraphCollectionType
+ {
+ DeletedParagraphMarkContent,
+ ParagraphFollowing,
+ Other
+ };
+
+ /// Accept deleted paragraphs.
+ ///
+ /// Group together all paragraphs that contain w:p/w:pPr/w:rPr/w:del elements. Make a
+ /// second group for the content element immediately following a paragraph that contains
+ /// a w:del element. The code uses the approach of dealing with paragraph content at
+ /// 'levels', ignoring paragraph content at other levels. Form a new paragraph that
+ /// contains the content of the grouped paragraphs with deleted paragraph marks, and the
+ /// content of the paragraph immediately following a paragraph that contains a deleted
+ /// paragraph mark. Include in the new paragraph the paragraph properties from the
+ /// paragraph following. When assembling the new paragraph, use a transform that collapses
+ /// the paragraph nodes when adding content, thereby preserving custom XML and content
+ /// controls.
+
+ private static void AnnotateBlockContentElements(XElement contentContainer)
+ {
+ // For convenience, there is a ParagraphInfo annotation on the contentContainer.
+ // It contains the same information as the ParagraphInfo annotation on the first
+ // paragraph.
+ if (contentContainer.Annotation<BlockContentInfo>() != null)
+ return;
+ XElement firstContentElement = contentContainer
+ .Elements()
+ .DescendantsAndSelf()
+ .FirstOrDefault(e => e.Name == W.p || e.Name == W.tbl);
+ if (firstContentElement == null)
+ return;
+
+ // Add the annotation on the contentContainer.
+ BlockContentInfo currentContentInfo = new BlockContentInfo()
+ {
+ PreviousBlockContentElement = null,
+ ThisBlockContentElement = firstContentElement,
+ NextBlockContentElement = null
+ };
+ // Add as annotation even though NextParagraph is not set yet.
+ contentContainer.AddAnnotation(currentContentInfo);
+ while (true)
+ {
+ currentContentInfo.ThisBlockContentElement.AddAnnotation(currentContentInfo);
+ // Find next sibling content element.
+ XElement nextContentElement = null;
+ XElement current = currentContentInfo.ThisBlockContentElement;
+ while (true)
+ {
+ nextContentElement = current
+ .ElementsAfterSelf()
+ .DescendantsAndSelf()
+ .FirstOrDefault(e => e.Name == W.p || e.Name == W.tbl);
+ if (nextContentElement != null)
+ {
+ currentContentInfo.NextBlockContentElement = nextContentElement;
+ break;
+ }
+ current = current.Parent;
+ // When we've backed up the tree to the contentContainer, we're done.
+ if (current == contentContainer)
+ return;
+ }
+ currentContentInfo = new BlockContentInfo()
+ {
+ PreviousBlockContentElement = currentContentInfo.ThisBlockContentElement,
+ ThisBlockContentElement = nextContentElement,
+ NextBlockContentElement = null
+ };
+ }
+ }
+
+ private static IEnumerable<BlockContentInfo> IterateBlockContentElements(XElement element)
+ {
+ XElement current = element.Elements().FirstOrDefault();
+ if (current == null)
+ yield break;
+ AnnotateBlockContentElements(element);
+ BlockContentInfo currentBlockContentInfo = element.Annotation<BlockContentInfo>();
+ if (currentBlockContentInfo != null)
+ {
+ while (true)
+ {
+ yield return currentBlockContentInfo;
+ if (currentBlockContentInfo.NextBlockContentElement == null)
+ yield break;
+ currentBlockContentInfo = currentBlockContentInfo.NextBlockContentElement.Annotation<BlockContentInfo>();
+ }
+ }
+ }
+
+ public static class PT
+ {
+ public static XNamespace pt = "http://www.codeplex.com/PowerTools/2009/RevisionAccepter";
+ public static XName UniqueId = pt + "UniqueId";
+ public static XName RunIds = pt + "RunIds";
+ }
+
+ private static void AnnotateRunElementsWithId(XElement element)
+ {
+ int runId = 0;
+ foreach (XElement e in element.Descendants().Where(e => e.Name == W.r))
+ {
+ if (e.Name == W.r)
+ e.Add(new XAttribute(PT.UniqueId, runId++));
+ }
+ }
+
+ private static void AnnotateContentControlsWithRunIds(XElement element)
+ {
+ int sdtId = 0;
+ foreach (XElement e in element.Descendants(W.sdt))
+ {
+ // old version
+ //e.Add(new XAttribute(PT.RunIds,
+ // e.Descendants(W.r).Select(r => r.Attribute(PT.UniqueId).Value).StringConcatenate(s => s + ",").Trim(',')),
+ // new XAttribute(PT.UniqueId, sdtId++));
+ e.Add(new XAttribute(PT.RunIds,
+ e.DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.r)
+ .Select(r => r.Attribute(PT.UniqueId).Value)
+ .StringConcatenate(s => s + ",")
+ .Trim(',')),
+ new XAttribute(PT.UniqueId, sdtId++));
+ }
+ }
+
+ private static XElement AddBlockLevelContentControls(XElement newDocument, XElement original)
+ {
+ var originalContentControls = original.Descendants(W.sdt).ToList();
+ var existingContentControls = newDocument.Descendants(W.sdt).ToList();
+ var contentControlsToAdd = originalContentControls
+ .Select(occ => occ.Attribute(PT.UniqueId).Value)
+ .Except(existingContentControls
+ .Select(ecc => ecc.Attribute(PT.UniqueId).Value));
+ foreach (var contentControl in originalContentControls
+ .Where(occ => contentControlsToAdd.Contains(occ.Attribute(PT.UniqueId).Value)))
+ {
+ // TODO - Need a slight modification here. If there is a paragraph
+ // in the content control that contains no runs, then the paragraph isn't included in the
+ // content control, because the following triggers off of runs.
+ // To see an example of this, see example document "NumberingParagraphPropertiesChange.docxs"
+
+ // find list of runs to surround
+ var runIds = contentControl.Attribute(PT.RunIds).Value.Split(',');
+ var runs = contentControl.Descendants(W.r).Where(r => runIds.Contains(r.Attribute(PT.UniqueId).Value));
+ // find the runs in the new document
+
+ var runsInNewDocument = runs.Select(r => newDocument.Descendants(W.r).First(z => z.Attribute(PT.UniqueId).Value == r.Attribute(PT.UniqueId).Value)).ToList();
+
+ // find common ancestor
+ List<XElement> runAncestorIntersection = null;
+ foreach (var run in runsInNewDocument)
+ {
+ if (runAncestorIntersection == null)
+ runAncestorIntersection = run.Ancestors().ToList();
+ else
+ runAncestorIntersection = run.Ancestors().Intersect(runAncestorIntersection).ToList();
+ }
+ if (runAncestorIntersection == null)
+ continue;
+ XElement commonAncestor = runAncestorIntersection.InDocumentOrder().Last();
+ // find child of common ancestor that contains first run
+ // find child of common ancestor that contains last run
+ // create new common ancestor:
+ // elements before first run child
+ // add content control, and runs from first run child to last run child
+ // elements after last run child
+ var firstRunChild = commonAncestor
+ .Elements()
+ .First(c => c.DescendantsAndSelf()
+ .Any(z => z.Name == W.r &&
+ z.Attribute(PT.UniqueId).Value == runsInNewDocument.First().Attribute(PT.UniqueId).Value));
+ var lastRunChild = commonAncestor
+ .Elements()
+ .First(c => c.DescendantsAndSelf()
+ .Any(z => z.Name == W.r &&
+ z.Attribute(PT.UniqueId).Value == runsInNewDocument.Last().Attribute(PT.UniqueId).Value));
+
+ /// If the list of runs for the content control is exactly the list of runs for the paragraph, then
+ /// create the content control surrounding the paragraph, not surrounding the runs.
+
+ if (commonAncestor.Name == W.p &&
+ commonAncestor.Elements()
+ .Where(e => e.Name != W.pPr &&
+ e.Name != W.commentRangeStart &&
+ e.Name != W.commentRangeEnd)
+ .FirstOrDefault() == firstRunChild &&
+ commonAncestor.Elements()
+ .Where(e => e.Name != W.pPr &&
+ e.Name != W.commentRangeStart &&
+ e.Name != W.commentRangeEnd)
+ .LastOrDefault() == lastRunChild)
+ {
+ // replace commonAncestor with content control containing commonAncestor
+ XElement newContentControl = new XElement(contentControl.Name,
+ contentControl.Attributes(),
+ contentControl.Elements().Where(e => e.Name != W.sdtContent),
+ new XElement(W.sdtContent, commonAncestor));
+
+ XElement newContentControlOrdered = new XElement(contentControl.Name,
+ contentControl.Attributes(),
+ contentControl.Elements().OrderBy(e =>
+ {
+ if (Order_sdt.ContainsKey(e.Name))
+ return Order_sdt[e.Name];
+ return 999;
+ }));
+
+ commonAncestor.ReplaceWith(newContentControlOrdered);
+ continue;
+ }
+
+ List<XElement> elementsBeforeRange = commonAncestor
+ .Elements()
+ .TakeWhile(e => e != firstRunChild)
+ .ToList();
+ List<XElement> elementsInRange = commonAncestor
+ .Elements()
+ .SkipWhile(e => e != firstRunChild)
+ .TakeWhile(e => e != lastRunChild.ElementsAfterSelf().FirstOrDefault())
+ .ToList();
+ List<XElement> elementsAfterRange = commonAncestor
+ .Elements()
+ .SkipWhile(e => e != lastRunChild.ElementsAfterSelf().FirstOrDefault())
+ .ToList();
+
+ // detatch from current parent
+ commonAncestor.Elements().Remove();
+
+ XElement newContentControl2 = new XElement(contentControl.Name,
+ contentControl.Attributes(),
+ contentControl.Elements().Where(e => e.Name != W.sdtContent),
+ new XElement(W.sdtContent, elementsInRange));
+
+ XElement newContentControlOrdered2 = new XElement(newContentControl2.Name,
+ newContentControl2.Attributes(),
+ newContentControl2.Elements().OrderBy(e =>
+ {
+ if (Order_sdt.ContainsKey(e.Name))
+ return Order_sdt[e.Name];
+ return 999;
+ }));
+
+ commonAncestor.Add(
+ elementsBeforeRange,
+ newContentControlOrdered2,
+ elementsAfterRange);
+ }
+ return newDocument;
+ }
+
+ private static Dictionary<XName, int> Order_sdt = new Dictionary<XName, int>
+ {
+ { W.sdtPr, 10 },
+ { W.sdtEndPr, 20 },
+ { W.sdtContent, 30 },
+ { W.bookmarkStart, 40 },
+ { W.bookmarkEnd, 50 },
+ };
+
+ private static XElement AcceptDeletedAndMoveFromParagraphMarks(XElement element)
+ {
+ AnnotateRunElementsWithId(element);
+ AnnotateContentControlsWithRunIds(element);
+ XElement newElement = (XElement)AcceptDeletedAndMoveFromParagraphMarksTransform(element);
+ XElement withBlockLevelContentControls = AddBlockLevelContentControls(newElement, element);
+ return withBlockLevelContentControls;
+ }
+
+ enum GroupingType
+ {
+ DeletedRange,
+ Other,
+ };
+
+ class GroupingInfo
+ {
+ public GroupingType GroupingType;
+ public int GroupingKey;
+ };
+
+ private static object AcceptDeletedAndMoveFromParagraphMarksTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (W.BlockLevelContentContainers.Contains(element.Name))
+ {
+ XElement bodySectPr = null;
+ if (element.Name == W.body)
+ bodySectPr = element.Element(W.sectPr);
+
+ int currentKey = 0;
+ var deletedParagraphGroupingInfo = new List<GroupingInfo>();
+
+ int state = 0; // 0 = in non deleted paragraphs
+ // 1 = in deleted paragraph
+ // 2 - paragraph following deleted paragraphs
+
+ foreach (var c in IterateBlockContentElements(element))
+ {
+ if (c.ThisBlockContentElement.Name == W.p)
+ {
+ bool paragraphMarkIsDeletedOrMovedFrom = c
+ .ThisBlockContentElement
+ .Elements(W.pPr)
+ .Elements(W.rPr)
+ .Elements()
+ .Where(e => e.Name == W.del || e.Name == W.moveFrom)
+ .Any();
+
+ if (paragraphMarkIsDeletedOrMovedFrom)
+ {
+ if (state == 0)
+ {
+ state = 1;
+ currentKey += 1;
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo() {
+ GroupingType = GroupingType.DeletedRange,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ else if (state == 1)
+ {
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.DeletedRange,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ else if (state == 2)
+ {
+ state = 1;
+ currentKey += 1;
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.DeletedRange,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ }
+
+ if (state == 0)
+ {
+ currentKey += 1;
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.Other,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ else if (state == 1)
+ {
+ state = 2;
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.DeletedRange,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ else if (state == 2)
+ {
+ state = 0;
+ currentKey += 1;
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.Other,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ }
+ else if (c.ThisBlockContentElement.Name == W.tbl || c.ThisBlockContentElement.Name.Namespace == M.m)
+ {
+ currentKey += 1;
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.Other,
+ GroupingKey = currentKey,
+ });
+ state = 0;
+ continue;
+ }
+ else
+ {
+ // otherwise keep the same state, put in the same group, and continue
+ deletedParagraphGroupingInfo.Add(
+ new GroupingInfo()
+ {
+ GroupingType = GroupingType.Other,
+ GroupingKey = currentKey,
+ });
+ continue;
+ }
+ }
+
+ var zipped = IterateBlockContentElements(element).Zip(deletedParagraphGroupingInfo, (blc, gi) => new
+ {
+ BlockLevelContent = blc,
+ GroupingInfo = gi,
+ });
+
+ var groupedParagraphs = zipped
+ .GroupAdjacent(z => z.GroupingInfo.GroupingKey);
+
+ // Create a new block level content container.
+ XElement newBlockLevelContentContainer = new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Where(e => e.Name == W.tcPr),
+ groupedParagraphs.Select((g, i) =>
+ {
+ if (g.First().GroupingInfo.GroupingType == GroupingType.DeletedRange)
+ {
+ XElement newParagraph = new XElement(W.p,
+#if false
+ // previously, this was set to g.First()
+ // however, this caused test [InlineData("RP/RP052-Deleted-Para-Mark.docx")] to lose paragraph numbering for a paragraph that we did not want to loose it for.
+ // the question is - when coalescing multiple paragraphs due to deleted paragraph marks, should we be taking the paragraph properties from the first or the last
+ // in the sequence of coalesced paragraph. It is possible that we should take Last when accepting revisions, but First when rejecting revisions.
+ g.First().BlockLevelContent.ThisBlockContentElement.Elements(W.pPr),
+#endif
+ g.Last().BlockLevelContent.ThisBlockContentElement.Elements(W.pPr),
+ g.Select(z => CollapseParagraphTransform(z.BlockLevelContent.ThisBlockContentElement)));
+
+ // if this contains the last paragraph in the document, and if there is no content,
+ // and if the paragraph mark is deleted, then nuke the paragraph.
+ var allIsDeleted = AllParaContentIsDeleted(newParagraph);
+ if (allIsDeleted &&
+ g.Last().BlockLevelContent.ThisBlockContentElement.Elements(W.pPr).Elements(W.rPr).Elements(W.del).Any() &&
+ (g.Last().BlockLevelContent.NextBlockContentElement == null ||
+ g.Last().BlockLevelContent.NextBlockContentElement.Name == W.tbl))
+ return null;
+
+ return (object)newParagraph;
+ }
+ else
+ {
+ return g.Select(z =>
+ {
+ var newEle = new XElement(z.BlockLevelContent.ThisBlockContentElement.Name,
+ z.BlockLevelContent.ThisBlockContentElement.Attributes(),
+ z.BlockLevelContent.ThisBlockContentElement.Nodes().Select(n => AcceptDeletedAndMoveFromParagraphMarksTransform(n)));
+ return newEle;
+ });
+ }
+ }),
+ bodySectPr);
+
+ return newBlockLevelContentContainer;
+ }
+
+ // Otherwise, identity clone.
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptDeletedAndMoveFromParagraphMarksTransform(n)));
+ }
+ return node;
+ }
+
+ // Determine if the paragraph contains any content that is not deleted.
+ private static bool AllParaContentIsDeleted(XElement p)
+ {
+ // needs collapse
+ // dir, bdo, sdt, ins, moveTo, smartTag
+ var testP = (XElement)CollapseTransform(p);
+
+ var childElements = testP.Elements();
+ var contentElements = childElements
+ .Where(ce =>
+ {
+ var b = IsRunContent(ce.Name);
+ if (b != null)
+ return (bool)b;
+ throw new Exception("Internal error 20, found element " + ce.Name.ToString());
+ });
+ if (contentElements.Any())
+ return false;
+ return true;
+ }
+
+ // dir, bdo, sdt, ins, moveTo, smartTag
+ private static object CollapseTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.dir ||
+ element.Name == W.bdr ||
+ element.Name == W.ins ||
+ element.Name == W.moveTo ||
+ element.Name == W.smartTag)
+ return element.Elements();
+
+ if (element.Name == W.sdt)
+ return element.Elements(W.sdtContent).Elements();
+
+ if (element.Name == W.pPr)
+ return null;
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => CollapseTransform(n)));
+ }
+ return node;
+ }
+
+ private static bool? IsRunContent(XName ceName)
+ {
+ // is content
+ // r, fldSimple, hyperlink, oMath, oMathPara, subDoc
+ if (ceName == W.r ||
+ ceName == W.fldSimple ||
+ ceName == W.hyperlink ||
+ ceName == W.subDoc ||
+ ceName == W.smartTag ||
+ ceName == W.smartTagPr ||
+ ceName.Namespace == M.m)
+ return true;
+
+ // not content
+ // bookmarkStart, bookmarkEnd, commentRangeStart, commentRangeEnd, del, moveFrom, proofErr
+ if (ceName == W.bookmarkStart ||
+ ceName == W.bookmarkEnd ||
+ ceName == W.commentRangeStart ||
+ ceName == W.commentRangeEnd ||
+ ceName == W.customXmlDelRangeStart ||
+ ceName == W.customXmlDelRangeEnd ||
+ ceName == W.customXmlInsRangeStart ||
+ ceName == W.customXmlInsRangeEnd ||
+ ceName == W.customXmlMoveFromRangeStart ||
+ ceName == W.customXmlMoveFromRangeEnd ||
+ ceName == W.customXmlMoveToRangeStart ||
+ ceName == W.customXmlMoveToRangeEnd ||
+ ceName == W.del ||
+ ceName == W.moveFrom ||
+ ceName == W.moveFromRangeStart ||
+ ceName == W.moveFromRangeEnd ||
+ ceName == W.moveToRangeStart ||
+ ceName == W.moveToRangeEnd ||
+ ceName == W.permStart ||
+ ceName == W.permEnd ||
+ ceName == W.proofErr)
+ return false;
+
+ return null;
+ }
+
+ private static IEnumerable<Tag> DescendantAndSelfTags(XElement element)
+ {
+ yield return new Tag
+ {
+ Element = element,
+ TagType = TagTypeEnum.Element
+ };
+ Stack<IEnumerator<XElement>> iteratorStack = new Stack<IEnumerator<XElement>>();
+ iteratorStack.Push(element.Elements().GetEnumerator());
+ while (iteratorStack.Count > 0)
+ {
+ if (iteratorStack.Peek().MoveNext())
+ {
+ XElement currentXElement = iteratorStack.Peek().Current;
+ if (!currentXElement.Nodes().Any())
+ {
+ yield return new Tag()
+ {
+ Element = currentXElement,
+ TagType = TagTypeEnum.EmptyElement
+ };
+ continue;
+ }
+ yield return new Tag()
+ {
+ Element = currentXElement,
+ TagType = TagTypeEnum.Element
+ };
+ iteratorStack.Push(currentXElement.Elements().GetEnumerator());
+ continue;
+ }
+ iteratorStack.Pop();
+ if (iteratorStack.Count > 0)
+ yield return new Tag()
+ {
+ Element = iteratorStack.Peek().Current,
+ TagType = TagTypeEnum.EndElement
+ };
+ }
+ yield return new Tag
+ {
+ Element = element,
+ TagType = TagTypeEnum.EndElement
+ };
+ }
+
+ private class PotentialInRangeElements
+ {
+ public List<XElement> PotentialStartElementTagsInRange;
+ public List<XElement> PotentialEndElementTagsInRange;
+
+ public PotentialInRangeElements()
+ {
+ PotentialStartElementTagsInRange = new List<XElement>();
+ PotentialEndElementTagsInRange = new List<XElement>();
+ }
+ }
+
+ private enum TagTypeEnum
+ {
+ Element,
+ EndElement,
+ EmptyElement
+ }
+
+ private class Tag
+ {
+ public XElement Element;
+ public TagTypeEnum TagType;
+ }
+
+ private static object AcceptDeletedAndMovedFromContentControlsTransform(XNode node,
+ XElement[] contentControlElementsToCollapse,
+ XElement[] moveFromElementsToDelete)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.sdt && contentControlElementsToCollapse.Contains(element))
+ return element
+ .Element(W.sdtContent)
+ .Nodes()
+ .Select(n => AcceptDeletedAndMovedFromContentControlsTransform(
+ n, contentControlElementsToCollapse, moveFromElementsToDelete));
+ if (moveFromElementsToDelete.Contains(element))
+ return null;
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptDeletedAndMovedFromContentControlsTransform(
+ n, contentControlElementsToCollapse, moveFromElementsToDelete)));
+ }
+ return node;
+ }
+
+ private static XElement AcceptDeletedAndMovedFromContentControls(XElement documentRootElement)
+ {
+ string wordProcessingNamespacePrefix = documentRootElement.GetPrefixOfNamespace(W.w);
+
+ // The following lists contain the elements that are between start/end elements.
+ List<XElement> startElementTagsInDeleteRange = new List<XElement>();
+ List<XElement> endElementTagsInDeleteRange = new List<XElement>();
+ List<XElement> startElementTagsInMoveFromRange = new List<XElement>();
+ List<XElement> endElementTagsInMoveFromRange = new List<XElement>();
+
+ // Following are the elements that *may* be in a range that has both start and end
+ // elements.
+ Dictionary<string, PotentialInRangeElements> potentialDeletedElements =
+ new Dictionary<string, PotentialInRangeElements>();
+ Dictionary<string, PotentialInRangeElements> potentialMoveFromElements =
+ new Dictionary<string, PotentialInRangeElements>();
+
+ foreach (var tag in DescendantAndSelfTags(documentRootElement))
+ {
+ if (tag.Element.Name == W.customXmlDelRangeStart)
+ {
+ string id = tag.Element.Attribute(W.id).Value;
+ potentialDeletedElements.Add(id, new PotentialInRangeElements());
+ continue;
+ }
+ if (tag.Element.Name == W.customXmlDelRangeEnd)
+ {
+ string id = tag.Element.Attribute(W.id).Value;
+ if (potentialDeletedElements.ContainsKey(id))
+ {
+ startElementTagsInDeleteRange.AddRange(
+ potentialDeletedElements[id].PotentialStartElementTagsInRange);
+ endElementTagsInDeleteRange.AddRange(
+ potentialDeletedElements[id].PotentialEndElementTagsInRange);
+ potentialDeletedElements.Remove(id);
+ }
+ continue;
+ }
+ if (tag.Element.Name == W.customXmlMoveFromRangeStart)
+ {
+ string id = tag.Element.Attribute(W.id).Value;
+ potentialMoveFromElements.Add(id, new PotentialInRangeElements());
+ continue;
+ }
+ if (tag.Element.Name == W.customXmlMoveFromRangeEnd)
+ {
+ string id = tag.Element.Attribute(W.id).Value;
+ if (potentialMoveFromElements.ContainsKey(id))
+ {
+ startElementTagsInMoveFromRange.AddRange(
+ potentialMoveFromElements[id].PotentialStartElementTagsInRange);
+ endElementTagsInMoveFromRange.AddRange(
+ potentialMoveFromElements[id].PotentialEndElementTagsInRange);
+ potentialMoveFromElements.Remove(id);
+ }
+ continue;
+ }
+ if (tag.Element.Name == W.sdt)
+ {
+ if (tag.TagType == TagTypeEnum.Element)
+ {
+ foreach (var id in potentialDeletedElements)
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ foreach (var id in potentialMoveFromElements)
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ continue;
+ }
+ if (tag.TagType == TagTypeEnum.EmptyElement)
+ {
+ foreach (var id in potentialDeletedElements)
+ {
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ }
+ foreach (var id in potentialMoveFromElements)
+ {
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ }
+ continue;
+ }
+ if (tag.TagType == TagTypeEnum.EndElement)
+ {
+ foreach (var id in potentialDeletedElements)
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ foreach (var id in potentialMoveFromElements)
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ continue;
+ }
+ throw new PowerToolsInvalidDataException("Should not have reached this point.");
+ }
+ if (potentialMoveFromElements.Count() > 0 &&
+ tag.Element.Name != W.moveFromRangeStart &&
+ tag.Element.Name != W.moveFromRangeEnd &&
+ tag.Element.Name != W.customXmlMoveFromRangeStart &&
+ tag.Element.Name != W.customXmlMoveFromRangeEnd)
+ {
+ if (tag.TagType == TagTypeEnum.Element)
+ {
+ foreach (var id in potentialMoveFromElements)
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ continue;
+ }
+ if (tag.TagType == TagTypeEnum.EmptyElement)
+ {
+ foreach (var id in potentialMoveFromElements)
+ {
+ id.Value.PotentialStartElementTagsInRange.Add(tag.Element);
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ }
+ continue;
+ }
+ if (tag.TagType == TagTypeEnum.EndElement)
+ {
+ foreach (var id in potentialMoveFromElements)
+ id.Value.PotentialEndElementTagsInRange.Add(tag.Element);
+ continue;
+ }
+ }
+ }
+
+ var contentControlElementsToCollapse = startElementTagsInDeleteRange
+ .Intersect(endElementTagsInDeleteRange)
+ .ToArray();
+ var elementsToDeleteBecauseMovedFrom = startElementTagsInMoveFromRange
+ .Intersect(endElementTagsInMoveFromRange)
+ .ToArray();
+ if (contentControlElementsToCollapse.Length > 0 ||
+ elementsToDeleteBecauseMovedFrom.Length > 0)
+ {
+ var newDoc = AcceptDeletedAndMovedFromContentControlsTransform(documentRootElement,
+ contentControlElementsToCollapse, elementsToDeleteBecauseMovedFrom);
+ return newDoc as XElement;
+ }
+ else
+ return documentRootElement;
+ }
+
+ private static object AcceptMoveFromRangesTransform(XNode node,
+ XElement[] elementsToDelete)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (elementsToDelete.Contains(element))
+ return null;
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n =>
+ AcceptMoveFromRangesTransform(n, elementsToDelete)));
+ }
+ return node;
+ }
+
+ private static object CoalesqueParagraphEndTagsInMoveFromTransform(XNode node,
+ IGrouping<MoveFromCollectionType, XElement> g)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ return new XElement(W.p,
+ element.Attributes(),
+ element.Elements(),
+ g.Skip(1).Select(p => CollapseParagraphTransform(p)));
+ else
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n =>
+ CoalesqueParagraphEndTagsInMoveFromTransform(n, g)));
+ }
+ return node;
+ }
+
+ private enum DeletedCellCollectionType
+ {
+ DeletedCell,
+ Other
+ };
+
+ // For each table row, group deleted cells plus the cell before any deleted cell.
+ // Produce a new cell that has gridSpan set appropriately for group, and clone everything
+ // else.
+ private static object AcceptDeletedCellsTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.tr)
+ {
+ var groupedCells = element
+ .Elements()
+ .GroupAdjacent(e =>
+ {
+ XElement cellAfter = e.ElementsAfterSelf(W.tc).FirstOrDefault();
+ bool cellAfterIsDeleted = cellAfter != null &&
+ cellAfter.Descendants(W.cellDel).Any();
+ if (e.Name == W.tc &&
+ (cellAfterIsDeleted || e.Descendants(W.cellDel).Any()))
+ {
+ var a = new
+ {
+ CollectionType = DeletedCellCollectionType.DeletedCell,
+ Disambiguator = new[] { e }
+ .Concat(e.SiblingsBeforeSelfReverseDocumentOrder())
+ .Where(z => z.Name == W.tc &&
+ !z.Descendants(W.cellDel).Any())
+ .FirstOrDefault()
+ };
+ return a;
+ }
+ var a2 = new
+ {
+ CollectionType = DeletedCellCollectionType.Other,
+ Disambiguator = e
+ };
+ return a2;
+ });
+ var tr = new XElement(W.tr,
+ element.Attributes(),
+ groupedCells.Select(g =>
+ {
+ if (g.Key.CollectionType == DeletedCellCollectionType.DeletedCell
+ && g.First().Descendants(W.cellDel).Any())
+ return null;
+ if (g.Key.CollectionType == DeletedCellCollectionType.Other)
+ return (object)g;
+ XElement gridSpanElement = g
+ .First()
+ .Elements(W.tcPr)
+ .Elements(W.gridSpan)
+ .FirstOrDefault();
+ int gridSpan = gridSpanElement != null ?
+ (int)gridSpanElement.Attribute(W.val) :
+ 1;
+ int newGridSpan = gridSpan + g.Count() - 1;
+ XElement currentTcPr = g.First().Elements(W.tcPr).FirstOrDefault();
+ XElement newTcPr = new XElement(W.tcPr,
+ currentTcPr != null ? currentTcPr.Attributes() : null,
+ new XElement(W.gridSpan,
+ new XAttribute(W.val, newGridSpan)),
+ currentTcPr.Elements().Where(e => e.Name != W.gridSpan));
+ var orderedTcPr = new XElement(W.tcPr,
+ newTcPr.Elements().OrderBy(e =>
+ {
+ if (Order_tcPr.ContainsKey(e.Name))
+ return Order_tcPr[e.Name];
+ return 999;
+ }));
+ XElement newTc = new XElement(W.tc,
+ orderedTcPr,
+ g.First().Elements().Where(e => e.Name != W.tcPr));
+ return (object)newTc;
+ }));
+ return tr;
+ }
+
+ // Identity clone
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AcceptDeletedCellsTransform(n)));
+ }
+ return node;
+ }
+
+#if false
+ <w:tr>
+ <w:tc>
+ <w:tcPr>
+ <w:tcW w:w="5016"
+ w:type="dxa" />
+ </w:tcPr>
+ </w:tc>
+ </w:tr>
+#endif
+ private static XName[] BlockLevelElements = new[] {
+ W.p,
+ W.tbl,
+ W.sdt,
+ W.del,
+ W.ins,
+ M.oMath,
+ M.oMathPara,
+ W.moveTo,
+ };
+
+ private static object RemoveRowsLeftEmptyByMoveFrom(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.tr)
+ {
+ var nonEmptyCells = element.Elements(W.tc).Any(tc => tc.Elements().Any(tcc => BlockLevelElements.Contains(tcc.Name)));
+ if (nonEmptyCells)
+ {
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RemoveRowsLeftEmptyByMoveFrom(n)));
+ }
+ return null;
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => RemoveRowsLeftEmptyByMoveFrom(n)));
+ }
+ return node;
+ }
+
+ public static XName[] TrackedRevisionsElements = new[]
+ {
+ W.cellDel,
+ W.cellIns,
+ W.cellMerge,
+ W.customXmlDelRangeEnd,
+ W.customXmlDelRangeStart,
+ W.customXmlInsRangeEnd,
+ W.customXmlInsRangeStart,
+ W.del,
+ W.delInstrText,
+ W.delText,
+ W.ins,
+ W.moveFrom,
+ W.moveFromRangeEnd,
+ W.moveFromRangeStart,
+ W.moveTo,
+ W.moveToRangeEnd,
+ W.moveToRangeStart,
+ W.numberingChange,
+ W.pPrChange,
+ W.rPrChange,
+ W.sectPrChange,
+ W.tblGridChange,
+ W.tblPrChange,
+ W.tblPrExChange,
+ W.tcPrChange,
+ W.trPrChange,
+ };
+
+ public static bool PartHasTrackedRevisions(OpenXmlPart part)
+ {
+ return part.GetXDocument()
+ .Descendants()
+ .Any(e => TrackedRevisionsElements.Contains(e.Name));
+ }
+
+ public static bool HasTrackedRevisions(WmlDocument document)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument wdoc = streamDoc.GetWordprocessingDocument())
+ {
+ return RevisionAccepter.HasTrackedRevisions(wdoc);
+ }
+ }
+ }
+
+ public static bool HasTrackedRevisions(WordprocessingDocument doc)
+ {
+ if (PartHasTrackedRevisions(doc.MainDocumentPart))
+ return true;
+ foreach (var part in doc.MainDocumentPart.HeaderParts)
+ if (PartHasTrackedRevisions(part))
+ return true;
+ foreach (var part in doc.MainDocumentPart.FooterParts)
+ if (PartHasTrackedRevisions(part))
+ return true;
+ if (doc.MainDocumentPart.EndnotesPart != null)
+ if (PartHasTrackedRevisions(doc.MainDocumentPart.EndnotesPart))
+ return true;
+ if (doc.MainDocumentPart.FootnotesPart != null)
+ if (PartHasTrackedRevisions(doc.MainDocumentPart.FootnotesPart))
+ return true;
+ return false;
+ }
+ }
+
+ public partial class WmlDocument : OpenXmlPowerToolsDocument
+ {
+ public WmlDocument AcceptRevisions(WmlDocument document)
+ {
+ return RevisionAccepter.AcceptRevisions(document);
+ }
+ public bool HasTrackedRevisions(WmlDocument document)
+ {
+ return RevisionAccepter.HasTrackedRevisions(document);
+ }
+ }
+
+ public class BlockContentInfo
+ {
+ public XElement PreviousBlockContentElement;
+ public XElement ThisBlockContentElement;
+ public XElement NextBlockContentElement;
+ }
+
+ public static class RevisionAccepterExtensions
+ {
+ private static void InitializeParagraphInfo(XElement contentContext)
+ {
+ if (!(W.BlockLevelContentContainers.Contains(contentContext.Name)))
+ throw new ArgumentException(
+ "GetParagraphInfo called for element that is not child of content container");
+ XElement prev = null;
+ foreach (var content in contentContext.Elements())
+ {
+ // This may return null, indicating that there is no descendant paragraph. For
+ // example, comment elements have no descendant elements.
+ XElement paragraph = content
+ .DescendantsAndSelf()
+ .Where(e => e.Name == W.p || e.Name == W.tc || e.Name == W.txbxContent)
+ .FirstOrDefault();
+ if (paragraph != null &&
+ (paragraph.Name == W.tc || paragraph.Name == W.txbxContent))
+ paragraph = null;
+ BlockContentInfo pi = new BlockContentInfo()
+ {
+ PreviousBlockContentElement = prev,
+ ThisBlockContentElement = paragraph
+ };
+ content.AddAnnotation(pi);
+ prev = content;
+ }
+ }
+
+ public static BlockContentInfo GetParagraphInfo(this XElement contentElement)
+ {
+ BlockContentInfo paragraphInfo = contentElement.Annotation<BlockContentInfo>();
+ if (paragraphInfo != null)
+ return paragraphInfo;
+ InitializeParagraphInfo(contentElement.Parent);
+ return contentElement.Annotation<BlockContentInfo>();
+ }
+
+ public static IEnumerable<XElement> ContentElementsBeforeSelf(this XElement element)
+ {
+ XElement current = element;
+ while (true)
+ {
+ BlockContentInfo pi = current.GetParagraphInfo();
+ if (pi.PreviousBlockContentElement == null)
+ yield break;
+ yield return pi.PreviousBlockContentElement;
+ current = pi.PreviousBlockContentElement;
+ }
+ }
+ }
+}
+
+/// Markup that this code processes:
+///
+/// delText
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: MovedText.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to w:t element
+///
+/// del (deleted run content)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements and descendant elements.
+/// Reject:
+/// Transform to w:ins element
+/// Then Accept
+///
+/// ins (inserted run content)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: InsertedParagraphsAndRuns.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Collapse these elements.
+/// Reject:
+/// Transform to w:del element, and child w:t transform to w:delText element
+/// Then Accept
+///
+/// ins (inserted paragraph)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: InsertedParagraphsAndRuns.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to w:del element
+/// Then Accept
+///
+/// del (deleted paragraph mark)
+/// Method: AcceptDeletedAndMoveFromParagraphMarksTransform
+/// Sample document: VariousTableRevisions.docx (deleted paragraph mark in paragraph in
+/// content control)
+/// Reviewed: tristan and zeyad ****************************************
+/// Semantics:
+/// Find all adjacent paragraps that have this element.
+/// Group adjacent paragraphs plus the paragraph following paragraph that has this element.
+/// Replace grouped paragraphs with a new paragraph containing the content from all grouped
+/// paragraphs. Use the paragraph properties from the first paragraph in the group.
+/// Reject:
+/// Transform to w:ins element
+/// Then Accept
+///
+/// del (deleted table row)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Match w:tr/w:trPr/w:del, remove w:tr.
+/// Reject:
+/// Transform to w:ins
+/// Then Accept
+///
+/// ins (inserted table row)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to w:del
+/// Then Accept
+///
+/// del (deleted math control character)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: DeletedMathControlCharacter.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Match m:f/m:fPr/m:ctrlPr/w:del, remove m:f.
+/// Reject:
+/// Transform to w:ins
+/// Then Accept
+///
+/// ins (inserted math control character)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: InsertedMathControlCharacter.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to w:del
+/// Then Accept
+///
+/// moveTo (move destination paragraph mark)
+/// Method: AcceptMoveFromMoveToTransform
+/// Sample document: MovedText.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to moveFrom
+/// Then Accept
+///
+/// moveTo (move destination run content)
+/// Method: AcceptMoveFromMoveToTransform
+/// Sample document: MovedText.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Collapse these elements.
+/// Reject:
+/// Transform to moveFrom
+/// Then Accept
+///
+/// moveFrom (move source paragraph mark)
+/// Methods: AcceptDeletedAndMoveFromParagraphMarksTransform, AcceptParagraphEndTagsInMoveFromTransform
+/// Sample document: MovedText.docx
+/// Reviewed: tristan and zeyad ****************************************
+/// Semantics:
+/// Find all adjacent paragraps that have this element or deleted paragraph mark.
+/// Group adjacent paragraphs plus the paragraph following paragraph that has this element.
+/// Replace grouped paragraphs with a new paragraph containing the content from all grouped
+/// paragraphs.
+/// This is handled in the same code that handles del (deleted paragraph mark).
+/// Reject:
+/// Transform to moveTo
+/// Then Accept
+///
+/// moveFrom (move source run content)
+/// Method: AcceptMoveFromMoveToTransform
+/// Sample document: MovedText.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to moveTo
+/// Then Accept
+///
+/// moveFromRangeStart
+/// moveFromRangeEnd
+/// Method: AcceptMoveFromRanges
+/// Sample document: MovedText.docx
+/// Semantics:
+/// Find pairs of elements. Remove all elements that have both start and end tags in a
+/// range.
+/// Reject:
+/// Transform to moveToRangeStart, moveToRangeEnd
+/// Then Accept
+///
+/// moveToRangeStart
+/// moveToRangeEnd
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: MovedText.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to moveFromRangeStart, moveFromRangeEnd
+/// Then Accept
+///
+/// customXmlDelRangeStart
+/// customXmlDelRangeEnd
+/// customXmlMoveFromRangeStart
+/// customXmlMoveFromRangeEnd
+/// Method: AcceptDeletedAndMovedFromContentControls
+/// Reviewed: tristan and zeyad ****************************************
+/// Semantics:
+/// Find pairs of start/end elements, matching id attributes. Collapse sdt
+/// elements that have both start and end tags in a range.
+/// Reject:
+/// Transform to customXmlInsRangeStart, customXmlInsRangeEnd, customXmlMoveToRangeStart, customXmlMoveToRangeEnd
+/// Then Accept
+///
+/// customXmlInsRangeStart
+/// customXmlInsRangeEnd
+/// customXmlMoveToRangeStart
+/// customXmlMoveToRangeEnd
+/// Method: AcceptAllOtherRevisionsTransform
+/// Reviewed: tristan and zeyad ****************************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to customXmlDelRangeStart, customXmlDelRangeEnd, customXmlMoveFromRangeStart, customXmlMoveFromRangeEnd
+/// Then Accept
+///
+/// delInstrText (deleted field code)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: NumberingParagraphPropertiesChange.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Transform to instrText
+/// Then Accept
+/// Note that instrText must be transformed to delInstrText when in a w:ins, in the same fashion that w:t must be transformed to w:delText when in w:ins
+///
+/// ins (inserted numbering properties)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: InsertedNumberingProperties.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject
+/// Remove the containing w:numPr
+///
+/// pPrChange (revision information for paragraph properties)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: ParagraphAndRunPropertyRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace pPr with the pPr in pPrChange
+///
+/// rPrChange (revision information for run properties)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: ParagraphAndRunPropertyRevisions.docx
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace rPr with the rPr in rPrChange
+///
+/// rPrChange (revision information for run properties on the paragraph mark)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: ParagraphAndRunPropertyRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace rPr with the rPr in rPrChange.
+///
+/// numberingChange (previous numbering field properties)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: NumberingFieldPropertiesChange.docx
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Remove these elements.
+/// These are there for numbering created via fields, and are not important.
+///
+/// numberingChange (previous paragraph numbering properties)
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: NumberingFieldPropertiesChange.docx
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Remove these elements.
+///
+/// sectPrChange
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: SectionPropertiesChange.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace sectPr with the sectPr in sectPrChange
+///
+/// tblGridChange
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: TableGridChange.docx
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace tblGrid with the tblGrid in tblGridChange
+///
+/// tblPrChange
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: TableGridChange.docx
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace tblPr with the tblPr in tblPrChange
+///
+/// tblPrExChange
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace tblPrEx with the tblPrEx in tblPrExChange
+///
+/// tcPrChange
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: TableGridChange.docx
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace tcPr with the tcPr in tcPrChange
+///
+/// trPrChange
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: VariousTableRevisions.docx
+/// Reviewed: zeyad ***************************
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// Replace trPr with the trPr in trPrChange
+///
+/// celDel
+/// Method: AcceptDeletedCellsTransform
+/// Sample document: HorizontallyMergedCells.docx
+/// Semantics:
+/// Group consecutive deleted cells, and remove them.
+/// Adjust the cell before deleted cells:
+/// Increase gridSpan by the number of deleted cells that are removed.
+/// Reject:
+/// Remove this element
+///
+/// celIns
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: HorizontallyMergedCells11.docx
+/// Semantics:
+/// Remove these elements.
+/// Reject:
+/// If a w:tc contains w:tcPr/w:cellIns, then remove the cell
+///
+/// cellMerge
+/// Method: AcceptAllOtherRevisionsTransform
+/// Sample document: MergedCell.docx
+/// Semantics:
+/// Transform cellMerge with a parent of tcPr, with attribute w:vMerge="rest"
+/// to <w:vMerge w:val="restart"/>.
+/// Transform cellMerge with a parent of tcPr, with attribute w:vMerge="cont"
+/// to <w:vMerge w:val="continue"/>
+///
+/// The following items need to be addressed in a future release:
+/// - inserted run inside deleted paragraph - moveTo is same as insert
+/// - must increase w:val attribute of the w:gridSpan element of the
+/// cell immediately preceding the group of deleted cells by the
+/// ***sum*** of the values of the w:val attributes of w:gridSpan
+/// elements of each of the deleted cells.
+
diff --git a/OpenXmlPowerTools/SSFormula.cs b/OpenXmlPowerTools/SSFormula.cs
new file mode 100644
index 0000000..802bdd4
--- /dev/null
+++ b/OpenXmlPowerTools/SSFormula.cs
@@ -0,0 +1,120 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ExcelFormula
+{
+ public class ParseFormula
+ {
+ ExcelFormula parser;
+
+ public ParseFormula(string formula)
+ {
+ parser = new ExcelFormula(formula, Console.Out);
+ bool parserResult = false;
+ try
+ {
+ parserResult = parser.Formula();
+ }
+ catch (Peg.Base.PegException)
+ {
+ }
+ if (!parserResult)
+ {
+ parser.Warning("Error processing " + formula);
+ }
+ }
+
+ public string ReplaceSheetName(string oldName, string newName)
+ {
+ StringBuilder text = new StringBuilder(parser.GetSource());
+ ReplaceNode(parser.GetRoot(), (int)EExcelFormula.SheetName, oldName, newName, text);
+ return text.ToString();
+ }
+
+ public string ReplaceRelativeCell(int rowOffset, int colOffset)
+ {
+ StringBuilder text = new StringBuilder(parser.GetSource());
+ ReplaceRelativeCell(parser.GetRoot(), rowOffset, colOffset, text);
+ return text.ToString();
+ }
+
+ // Recursive function that will replace values from last to first
+ private void ReplaceNode(Peg.Base.PegNode node, int id, string oldName, string newName, StringBuilder text)
+ {
+ if (node.next_ != null)
+ ReplaceNode(node.next_, id, oldName, newName, text);
+ if (node.id_ == id && parser.GetSource().Substring(node.match_.posBeg_, node.match_.Length) == oldName)
+ {
+ text.Remove(node.match_.posBeg_, node.match_.Length);
+ text.Insert(node.match_.posBeg_, newName);
+ }
+ else if (node.child_ != null)
+ ReplaceNode(node.child_, id, oldName, newName, text);
+ }
+
+ // Recursive function that will adjust relative cells from last to first
+ private void ReplaceRelativeCell(Peg.Base.PegNode node, int rowOffset, int colOffset, StringBuilder text)
+ {
+ if (node.next_ != null)
+ ReplaceRelativeCell(node.next_, rowOffset, colOffset, text);
+ if (node.id_ == (int)EExcelFormula.A1Row && parser.GetSource().Substring(node.match_.posBeg_, 1) != "$")
+ {
+ int rowNumber = Convert.ToInt32(parser.GetSource().Substring(node.match_.posBeg_, node.match_.Length));
+ text.Remove(node.match_.posBeg_, node.match_.Length);
+ text.Insert(node.match_.posBeg_, Convert.ToString(rowNumber + rowOffset));
+ }
+ else if (node.id_ == (int)EExcelFormula.A1Column && parser.GetSource().Substring(node.match_.posBeg_, 1) != "$")
+ {
+ int colNumber = GetColumnNumber(parser.GetSource().Substring(node.match_.posBeg_, node.match_.Length));
+ text.Remove(node.match_.posBeg_, node.match_.Length);
+ text.Insert(node.match_.posBeg_, GetColumnId(colNumber + colOffset));
+ }
+ else if (node.child_ != null)
+ ReplaceRelativeCell(node.child_, rowOffset, colOffset, text);
+ }
+
+ // Converts the column reference string to a column number (e.g. A -> 1, B -> 2)
+ private static int GetColumnNumber(string cellReference)
+ {
+ int columnNumber = 0;
+ foreach (char c in cellReference)
+ {
+ if (Char.IsLetter(c))
+ columnNumber = columnNumber * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
+ }
+ return columnNumber;
+ }
+
+ // Translates the column number to the column reference string (e.g. 1 -> A, 2-> B)
+ private static string GetColumnId(int columnNumber)
+ {
+ string result = "";
+ do
+ {
+ result = ((char)((columnNumber - 1) % 26 + (int)'A')).ToString() + result;
+ columnNumber = (columnNumber - 1) / 26;
+ } while (columnNumber != 0);
+ return result;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/ScalarTypes.cs b/OpenXmlPowerTools/ScalarTypes.cs
new file mode 100644
index 0000000..d8e3768
--- /dev/null
+++ b/OpenXmlPowerTools/ScalarTypes.cs
@@ -0,0 +1,69 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections;
+using System.Collections.ObjectModel;
+
+namespace OpenXmlPowerTools
+{
+ internal static class DefaultScalarTypes
+ {
+ private static readonly Hashtable defaultScalarTypesHash;
+ internal static bool IsTypeInList(Collection<string> typeNames)
+ {
+ string text = PSObjectIsOfExactType(typeNames);
+ return !string.IsNullOrEmpty(text) && (PSObjectIsEnum(typeNames) || DefaultScalarTypes.defaultScalarTypesHash.ContainsKey(text));
+ }
+
+ static DefaultScalarTypes()
+ {
+ DefaultScalarTypes.defaultScalarTypesHash = new Hashtable(StringComparer.OrdinalIgnoreCase);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.String", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.SByte", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Byte", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Int16", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.UInt16", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Int32", 10);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.UInt32", 10);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Int64", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.UInt64", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Char", 1);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Single", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Double", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Boolean", 5);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Decimal", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.IntPtr", null);
+ DefaultScalarTypes.defaultScalarTypesHash.Add("System.Security.SecureString", null);
+ }
+
+ internal static string PSObjectIsOfExactType(Collection<string> typeNames)
+ {
+ if (typeNames.Count != 0)
+ {
+ return typeNames[0];
+ }
+ return null;
+ }
+
+ internal static bool PSObjectIsEnum(Collection<string> typeNames)
+ {
+ return typeNames.Count >= 2 && !string.IsNullOrEmpty(typeNames[1]) && string.Equals(typeNames[1], "System.Enum", StringComparison.Ordinal);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/SmlCellFormatter.cs b/OpenXmlPowerTools/SmlCellFormatter.cs
new file mode 100644
index 0000000..8934cdd
--- /dev/null
+++ b/OpenXmlPowerTools/SmlCellFormatter.cs
@@ -0,0 +1,276 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Windows.Forms;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class SmlCellFormatter
+ {
+ private enum CellType
+ {
+ General,
+ Number,
+ Date,
+ };
+
+ private class FormatConfig
+ {
+ public CellType CellType;
+ public string FormatCode;
+ }
+
+ private static Dictionary<string, FormatConfig> ExcelFormatCodeToNetFormatCodeExceptionMap = new Dictionary<string, FormatConfig>()
+ {
+ {
+ "# ?/?",
+ new FormatConfig
+ {
+ CellType = SmlCellFormatter.CellType.Number,
+ FormatCode = "0.00",
+ }
+ },
+ {
+ "# ??/??",
+ new FormatConfig
+ {
+ CellType = SmlCellFormatter.CellType.Number,
+ FormatCode = "0.00",
+ }
+ },
+ };
+
+ // Up to four sections of format codes can be specified. The format codes, separated by semicolons, define the
+ // formats for positive numbers, negative numbers, zero values, and text, in that order. If only two sections are
+ // specified, the first is used for positive numbers and zeros, and the second is used for negative numbers. If only
+ // one section is specified, it is used for all numbers. To skip a section, the ending semicolon for that section shall
+ // be written.
+ public static string FormatCell(string formatCode, string value, out string color)
+ {
+ color = null;
+
+ if (formatCode == null)
+ formatCode = "General";
+
+ var splitFormatCode = formatCode.Split(';');
+ if (splitFormatCode.Length == 1)
+ {
+ double dv;
+ if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dv))
+ {
+ return FormatDouble(formatCode, dv, out color);
+ }
+ return value;
+ }
+ if (splitFormatCode.Length == 2)
+ {
+ double dv;
+ if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dv))
+ {
+ if (dv > 0)
+ {
+ return FormatDouble(splitFormatCode[0], dv, out color);
+ }
+ else
+ {
+ return FormatDouble(splitFormatCode[1], dv, out color);
+ }
+ }
+ return value;
+
+ }
+ // positive, negative, zero, text
+ // _("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)
+ if (splitFormatCode.Length == 4)
+ {
+ double dv;
+ if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out dv))
+ {
+ if (dv > 0)
+ {
+ var z1 = FormatDouble(splitFormatCode[0], dv, out color);
+ return z1;
+ }
+ else if (dv < 0)
+ {
+ var z2 = FormatDouble(splitFormatCode[1], dv, out color);
+ return z2;
+ }
+ else // == 0
+ {
+ var z3 = FormatDouble(splitFormatCode[2], dv, out color);
+ return z3;
+ }
+ }
+ string fmt = splitFormatCode[3].Replace("@", "{0}").Replace("\"", "");
+ try
+ {
+ var s = string.Format(fmt, value);
+ return s;
+ }
+ catch (Exception)
+ {
+ return value;
+ }
+ }
+ return value;
+ }
+
+ static Regex UnderRegex = new Regex("_.");
+
+ // The following Regex transforms currency specifies into a character / string
+ // that string.Format can use to properly produce the correct text.
+ // "[$£-809]" => "£"
+ // "[$€-2]" => "€"
+ // "[$¥-804]" => "¥
+ // "[$CHF-100C]" => "CHF"
+ static string s_CurrRegex = @"\[\$(?<curr>.*-).*\]";
+
+ private static string ConvertFormatCode(string formatCode)
+ {
+ var newFormatCode = formatCode
+ .Replace("mmm-", "MMM-")
+ .Replace("-mmm", "-MMM")
+ .Replace("mm-", "MM-")
+ .Replace("mmmm", "MMMM")
+ .Replace("AM/PM", "tt")
+ .Replace("m/", "M/")
+ .Replace("*", "")
+ .Replace("?", "#")
+ ;
+ var withTrimmedUnderscores = UnderRegex.Replace(newFormatCode, "");
+ var withTransformedCurrency = Regex.Replace(withTrimmedUnderscores, s_CurrRegex, m => m.Groups[1].Value.TrimEnd('-'));
+ return withTransformedCurrency;
+ }
+
+ private static string[] ValidColors = new[] {
+ "Black",
+ "Blue",
+ "Cyan",
+ "Green",
+ "Magenta",
+ "Red",
+ "White",
+ "Yellow",
+ };
+
+ private static string FormatDouble(string formatCode, double dv, out string color)
+ {
+ color = null;
+ var trimmed = formatCode.Trim();
+ if (trimmed.StartsWith("[") &&
+ trimmed.Contains("]"))
+ {
+ var colorLen = trimmed.IndexOf(']');
+ color = trimmed.Substring(1, colorLen - 1);
+ if (ValidColors.Contains(color) ||
+ color.StartsWith("Color"))
+ {
+ if (color.StartsWith("Color"))
+ {
+ var idxStr = color.Substring(5);
+ int colorIdx;
+ if (int.TryParse(idxStr, out colorIdx))
+ {
+ if (colorIdx < SmlDataRetriever.IndexedColors.Length)
+ color = SmlDataRetriever.IndexedColors[colorIdx];
+ else
+ color = null;
+ }
+ }
+ formatCode = trimmed.Substring(colorLen + 1);
+ }
+ else
+ color = null;
+ }
+
+
+ if (formatCode == "General")
+ return dv.ToString(CultureInfo.InvariantCulture);
+ bool isDate = IsFormatCodeForDate(formatCode);
+ var cfc = ConvertFormatCode(formatCode);
+ if (isDate)
+ {
+ DateTime thisDate;
+ try
+ {
+ thisDate = DateTime.FromOADate(dv);
+ }
+ catch (ArgumentException)
+ {
+ return dv.ToString(CultureInfo.InvariantCulture);
+ }
+ if (cfc.StartsWith("[h]"))
+ {
+ DateTime zeroHour = new DateTime(1899, 12, 30, 0, 0, 0);
+ int deltaInHours = (int)((thisDate - zeroHour).TotalHours);
+ var newCfc = cfc.Substring(3);
+ var s = (deltaInHours.ToString() + thisDate.ToString(newCfc)).Trim();
+ return s;
+ }
+ if (cfc.EndsWith(".0"))
+ {
+ var cfc2 = cfc.Replace(".0", ":fff");
+ var s4 = thisDate.ToString(cfc2).Trim();
+ return s4;
+ }
+ var s2 = thisDate.ToString(cfc, CultureInfo.InvariantCulture).Trim();
+ return s2;
+ }
+ if (ExcelFormatCodeToNetFormatCodeExceptionMap.ContainsKey(formatCode))
+ {
+ FormatConfig fc = ExcelFormatCodeToNetFormatCodeExceptionMap[formatCode];
+ var s = dv.ToString(fc.FormatCode, CultureInfo.InvariantCulture).Trim();
+ return s;
+ }
+ if ((cfc.Contains('(') && cfc.Contains(')')) || cfc.Contains('-'))
+ {
+ var s3 = (-dv).ToString(cfc, CultureInfo.InvariantCulture).Trim();
+ return s3;
+ }
+ else
+ {
+ var s4 = dv.ToString(cfc, CultureInfo.InvariantCulture).Trim();
+ return s4;
+ }
+ }
+
+ private static bool IsFormatCodeForDate(string formatCode)
+ {
+ if (formatCode == "General")
+ return false;
+ return formatCode.Contains("m") ||
+ formatCode.Contains("d") ||
+ formatCode.Contains("y") ||
+ formatCode.Contains("h") ||
+ formatCode.Contains("s") ||
+ formatCode.Contains("AM") ||
+ formatCode.Contains("PM");
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/SmlDataRetriever.cs b/OpenXmlPowerTools/SmlDataRetriever.cs
new file mode 100644
index 0000000..7f43d9c
--- /dev/null
+++ b/OpenXmlPowerTools/SmlDataRetriever.cs
@@ -0,0 +1,1107 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.IO;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ public class SmlDataRetriever
+ {
+ public static XElement RetrieveSheet(SmlDocument smlDoc, string sheetName)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ return RetrieveSheet(sDoc, sheetName);
+ }
+ }
+ }
+
+ public static XElement RetrieveSheet(string fileName, string sheetName)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ return RetrieveSheet(sDoc, sheetName);
+ }
+ }
+
+ public static XElement RetrieveSheet(SpreadsheetDocument sDoc, string sheetName)
+ {
+ var wbXdoc = sDoc.WorkbookPart.GetXDocument();
+ var sheet = wbXdoc
+ .Root
+ .Elements(S.sheets)
+ .Elements(S.sheet)
+ .FirstOrDefault(s => (string)s.Attribute("name") == sheetName);
+ if (sheet == null)
+ throw new ArgumentException("Invalid sheet name passed to RetrieveSheet", "sheetName");
+ var range = "A1:XFD1048576";
+ int leftColumn, topRow, rightColumn, bottomRow;
+ XlsxTables.ParseRange(range, out leftColumn, out topRow, out rightColumn, out bottomRow);
+ return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow);
+ }
+
+ public static XElement RetrieveRange(SmlDocument smlDoc, string sheetName, string range)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ return RetrieveRange(sDoc, sheetName, range);
+ }
+ }
+ }
+
+ public static XElement RetrieveRange(string fileName, string sheetName, string range)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ return RetrieveRange(sDoc, sheetName, range);
+ }
+ }
+
+ public static XElement RetrieveRange(SpreadsheetDocument sDoc, string sheetName, string range)
+ {
+ int leftColumn, topRow, rightColumn, bottomRow;
+ XlsxTables.ParseRange(range, out leftColumn, out topRow, out rightColumn, out bottomRow);
+ return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow);
+ }
+
+ public static XElement RetrieveRange(SmlDocument smlDoc, string sheetName, int leftColumn, int topRow, int rightColumn, int bottomRow)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow);
+ }
+ }
+ }
+
+ public static XElement RetrieveRange(string fileName, string sheetName, int leftColumn, int topRow, int rightColumn, int bottomRow)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ return RetrieveRange(sDoc, sheetName, leftColumn, topRow, rightColumn, bottomRow);
+ }
+ }
+
+ public static XElement RetrieveRange(SpreadsheetDocument sDoc, string sheetName, int leftColumn, int topRow, int rightColumn, int bottomRow)
+ {
+ var wbXdoc = sDoc.WorkbookPart.GetXDocument();
+ var sheet = wbXdoc
+ .Root
+ .Elements(S.sheets)
+ .Elements(S.sheet)
+ .FirstOrDefault(s => (string)s.Attribute("name") == sheetName);
+ if (sheet == null)
+ throw new ArgumentException("Invalid sheet name passed to RetrieveRange", "sheetName");
+ var rId = (string)sheet.Attribute(R.id);
+ if (rId == null)
+ throw new FileFormatException("Invalid spreadsheet");
+ var sheetPart = sDoc.WorkbookPart.GetPartById(rId);
+ if (sheetPart == null)
+ throw new FileFormatException("Invalid spreadsheet");
+ var shXDoc = sheetPart.GetXDocument();
+
+ if (sDoc.WorkbookPart.WorkbookStylesPart == null)
+ throw new FileFormatException("Invalid spreadsheet. No WorkbookStylesPart.");
+ var styleXDoc = sDoc.WorkbookPart.WorkbookStylesPart.GetXDocument();
+
+ // if there is no shared string table, sharedStringTable will be null
+ // it will only be used if there is a cell type == "s", in which case, referencing this
+ // part would indicate an invalid spreadsheet.
+ SharedStringTablePart sharedStringTable = sDoc.WorkbookPart.SharedStringTablePart;
+
+ FixUpCellsThatHaveNoRAtt(shXDoc);
+
+ // assemble the transform
+ var sheetData = shXDoc
+ .Root
+ .Elements(S.sheetData)
+ .Elements(S.row)
+ .Select(row =>
+ {
+ // filter
+ string ra = (string)row.Attribute("r");
+ if (ra == null)
+ return null;
+ int rowNbr;
+ if (!int.TryParse(ra, out rowNbr))
+ return null;
+ if (rowNbr < topRow)
+ return null;
+ if (rowNbr > bottomRow)
+ return null;
+
+ var cells = row
+ .Elements(S.c)
+ .Select(cell =>
+ {
+ var cellAddress = (string)cell.Attribute("r");
+ if (cellAddress == null)
+ throw new FileFormatException("Invalid spreadsheet - cell does not have r attribute.");
+ var splitCellAddress = XlsxTables.SplitAddress(cellAddress);
+ var columnAddress = splitCellAddress[0];
+ var columnIndex = XlsxTables.ColumnAddressToIndex(columnAddress);
+
+ // filter
+ if (columnIndex < leftColumn || columnIndex > rightColumn)
+ return null;
+
+ var cellType = (string)cell.Attribute("t");
+ string sharedString = null;
+ if (cellType == "s")
+ {
+ int sharedStringIndex;
+ string sharedStringBeforeParsing = (string)cell.Element(S.v);
+ if (sharedStringBeforeParsing == null)
+ sharedStringBeforeParsing = (string)cell.Elements(S._is).Elements(S.t).FirstOrDefault();
+ if (sharedStringBeforeParsing == null)
+ throw new FileFormatException("Invalid document");
+ if (!int.TryParse(sharedStringBeforeParsing, out sharedStringIndex))
+ throw new FileFormatException("Invalid document");
+ XElement sharedStringElement = null;
+ if (sharedStringTable == null)
+ throw new FileFormatException("Invalid spreadsheet. Shared string, but no Shared String Part.");
+ sharedStringElement =
+ sharedStringTable
+ .GetXDocument()
+ .Root
+ .Elements(S.si)
+ .Skip(sharedStringIndex)
+ .FirstOrDefault();
+ if (sharedStringElement == null)
+ throw new FileFormatException("Invalid spreadsheet. Shared string reference not valid.");
+ sharedString =
+ sharedStringElement
+ .Descendants(S.t)
+ .StringConcatenate(e => (string)e);
+ }
+
+ if (sharedString != null)
+ {
+ XElement cellProps = GetCellProps_NotInTable(sDoc, styleXDoc, cell);
+ string value = sharedString;
+ string displayValue;
+ string color = null;
+ if (cellProps != null)
+ displayValue = SmlCellFormatter.FormatCell(
+ (string)cellProps.Attribute("formatCode"),
+ value,
+ out color);
+ else
+ displayValue = value;
+ XElement newCell1 = new XElement("Cell",
+ new XAttribute("Ref", (string)cell.Attribute("r")),
+ new XAttribute("ColumnId", columnAddress),
+ new XAttribute("ColumnNumber", columnIndex),
+ cell.Attribute("f") != null ? new XAttribute("Formula", (string)cell.Attribute("f")) : null,
+ cell.Attribute("s") != null ? new XAttribute("Style", (string)cell.Attribute("s")) : null,
+ cell.Attribute("t") != null ? new XAttribute("Type", (string)cell.Attribute("t")) : null,
+ cellProps,
+ new XElement("Value", value),
+ new XElement("DisplayValue", displayValue),
+ color != null ? new XElement("DisplayColor", color) : null
+ );
+ return newCell1;
+ }
+ else
+ {
+ var type = (string)cell.Attribute("t");
+ XElement value = new XElement("Value", cell.Value);
+ if (type != null && type == "inlineStr")
+ {
+ type = "s";
+ }
+ XAttribute typeAttr = null;
+ if (type != null)
+ typeAttr = new XAttribute("Type", type);
+
+ XElement cellProps = GetCellProps_NotInTable(sDoc, styleXDoc, cell);
+ string displayValue;
+ string color = null;
+ if (cellProps != null)
+ displayValue = SmlCellFormatter.FormatCell(
+ (string)cellProps.Attribute("formatCode"),
+ cell.Value,
+ out color);
+ else
+ displayValue = displayValue = SmlCellFormatter.FormatCell(
+ (string)"General",
+ cell.Value,
+ out color);
+ XElement newCell2 = new XElement("Cell",
+ new XAttribute("Ref", (string)cell.Attribute("r")),
+ new XAttribute("ColumnId", columnAddress),
+ new XAttribute("ColumnNumber", columnIndex),
+ typeAttr,
+ cell.Attribute("f") != null ? new XAttribute("Formula", (string)cell.Attribute("f")) : null,
+ cell.Attribute("s") != null ? new XAttribute("Style", (string)cell.Attribute("s")) : null,
+ cellProps,
+ value,
+ new XElement("DisplayValue", displayValue),
+ color != null ? new XElement("DisplayColor", color) : null);
+ return newCell2;
+ }
+ });
+ XElement dataRow = new XElement("Row",
+ row.Attribute("r") != null ? new XAttribute("RowNumber", (int)row.Attribute("r")) : null,
+ cells);
+ return dataRow;
+ });
+
+ var dataProps = GetDataProps(shXDoc);
+ XElement data = new XElement("Data",
+ dataProps,
+ sheetData);
+ return data;
+ }
+
+ // Sometimes encounter cells that have no r attribute, so infer it if possible.
+ // These are invalid spreadsheets, but attempt to get the data anyway.
+ private static void FixUpCellsThatHaveNoRAtt(XDocument shXDoc)
+ {
+ // if there are any rows that have all cells with no r attribute, then fix them up
+ var invalidRows = shXDoc
+ .Descendants(S.row)
+ .Where(r => ! r.Elements(S.c).Any(c => c.Attribute("r") != null))
+ .ToList();
+
+ foreach (var row in invalidRows)
+ {
+ var rowNumberStr = (string)row.Attribute("r");
+ var colNumber = 0;
+ foreach (var cell in row.Elements(S.c))
+ {
+ var newCellRef = XlsxTables.IndexToColumnAddress(colNumber) + rowNumberStr;
+ cell.Add(new XAttribute("r", newCellRef));
+ }
+ }
+
+ // repeat iteratively until no further fixes can be made
+ while (true)
+ {
+ var invalidCells = shXDoc
+ .Descendants(S.c)
+ .Where(c => c.Attribute("r") == null)
+ .ToList();
+
+ bool didFixup = false;
+ foreach (var cell in invalidCells)
+ {
+ var followingCell = cell.ElementsAfterSelf(S.c).FirstOrDefault();
+ if (followingCell != null)
+ {
+ var followingR = (string)followingCell.Attribute("r");
+ if (followingR != null)
+ {
+ var spl = XlsxTables.SplitAddress(followingR);
+ var colIdxFollowing = XlsxTables.ColumnAddressToIndex(spl[0]);
+ var newRef = XlsxTables.IndexToColumnAddress(colIdxFollowing - 1) + spl[1];
+ cell.Add(new XAttribute("r", newRef));
+ didFixup = true;
+ }
+ else
+ {
+ didFixup = FixUpBasedOnPrecedingCell(didFixup, cell);
+ }
+ }
+ else
+ {
+ didFixup = FixUpBasedOnPrecedingCell(didFixup, cell);
+ }
+ }
+ if (!didFixup)
+ break;
+ }
+ }
+
+ private static bool FixUpBasedOnPrecedingCell(bool didFixup, XElement cell)
+ {
+ XElement precedingCell = GetPrevousElement(cell);
+ if (precedingCell != null)
+ {
+ var precedingR = (string)precedingCell.Attribute("r");
+ if (precedingR != null)
+ {
+ var spl = XlsxTables.SplitAddress(precedingR);
+ var colIdxFollowing = XlsxTables.ColumnAddressToIndex(spl[0]);
+ var newRef = XlsxTables.IndexToColumnAddress(colIdxFollowing + 1) + spl[1];
+ cell.Add(new XAttribute("r", newRef));
+ didFixup = true;
+ }
+ }
+ return didFixup;
+ }
+
+ private static XElement GetPrevousElement(XElement element)
+ {
+ XElement previousElement = null;
+ XNode currentNode = element;
+ while (true)
+ {
+ if (currentNode.PreviousNode == null)
+ return null;
+ previousElement = currentNode.PreviousNode as XElement;
+ if (previousElement != null)
+ return previousElement;
+ currentNode = currentNode.PreviousNode;
+ }
+ }
+
+ public static XElement RetrieveTable(SmlDocument smlDoc, string sheetName, string tableName)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ return RetrieveTable(sDoc, tableName);
+ }
+ }
+ }
+
+ public static XElement RetrieveTable(string fileName, string tableName)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ return RetrieveTable(sDoc, tableName);
+ }
+ }
+
+ public static XElement RetrieveTable(SpreadsheetDocument sDoc, string tableName)
+ {
+ var table = sDoc.Table(tableName);
+ if (table == null)
+ throw new ArgumentException("Table not found", "tableName");
+
+ var styleXDoc = sDoc.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ var r = table.Ref;
+ int leftColumn, topRow, rightColumn, bottomRow;
+ XlsxTables.ParseRange(r, out leftColumn, out topRow, out rightColumn, out bottomRow);
+ var shXDoc = table.Parent.GetXDocument();
+
+ FixUpCellsThatHaveNoRAtt(shXDoc);
+
+ // assemble the transform
+ var columns = new XElement("Columns",
+ table.TableColumns().Select(tc =>
+ {
+ var colXElement = new XElement("Column",
+ tc.Name != null ? new XAttribute("Name", tc.Name) : null,
+ tc.UniqueName != null ? new XAttribute("UniqueName", tc.UniqueName) : null,
+ new XAttribute("ColumnIndex", tc.ColumnIndex),
+ new XAttribute("Id", tc.Id),
+ tc.DataDxfId != null ? new XAttribute("DataDxfId", tc.DataDxfId) : null,
+ tc.QueryTableFieldId != null ? new XAttribute("QueryTableFieldId", tc.QueryTableFieldId) : null);
+ return colXElement;
+ }));
+
+ var dataProps = GetDataProps(shXDoc);
+ var data = new XElement("Data",
+ dataProps,
+ table.TableRows().Select(tr =>
+ {
+ int rowRef;
+ if (!int.TryParse(tr.Row.RowId, out rowRef))
+ throw new FileFormatException("Invalid spreadsheet");
+
+ // filter
+ if (rowRef < topRow || rowRef > bottomRow)
+ return null;
+
+ var cellData = tr.Row.Cells().Select(tc =>
+ {
+ // filter
+ var columnIndex = tc.ColumnIndex;
+ if (columnIndex < leftColumn || columnIndex > rightColumn)
+ return null;
+
+ XElement cellProps = GetCellProps_InTable(sDoc, styleXDoc, table, tc);
+ if (tc.SharedString != null)
+ {
+ string displayValue;
+ string color = null;
+ if (cellProps != null)
+ displayValue = SmlCellFormatter.FormatCell(
+ (string)cellProps.Attribute("formatCode"),
+ tc.SharedString,
+ out color);
+ else
+ displayValue = tc.SharedString;
+ XElement newCell1 = new XElement("Cell",
+ tc.CellElement != null ? new XAttribute("Ref", (string)tc.CellElement.Attribute("r")) : null,
+ tc.ColumnAddress != null ? new XAttribute("ColumnId", tc.ColumnAddress) : null,
+ new XAttribute("ColumnNumber", tc.ColumnIndex),
+ tc.Type != null ? new XAttribute("Type", "s") : null,
+ tc.Formula != null ? new XAttribute("Formula", tc.Formula) : null,
+ tc.Style != null ? new XAttribute("Style", tc.Style) : null,
+ cellProps,
+ new XElement("Value", tc.SharedString),
+ new XElement("DisplayValue", displayValue),
+ color != null ? new XElement("DisplayColor", color) : null);
+ return newCell1;
+ }
+ else
+ {
+ XAttribute type = null;
+ if (tc.Type != null)
+ {
+ if (tc.Type == "inlineStr")
+ type = new XAttribute("Type", "s");
+ else
+ type = new XAttribute("Type", tc.Type);
+ }
+ string displayValue;
+ string color = null;
+ if (cellProps != null)
+ displayValue = SmlCellFormatter.FormatCell(
+ (string)cellProps.Attribute("formatCode"),
+ tc.Value,
+ out color);
+ else
+ displayValue = SmlCellFormatter.FormatCell(
+ (string)"General",
+ tc.Value,
+ out color);
+ XElement newCell = new XElement("Cell",
+ tc.CellElement != null ? new XAttribute("Ref", (string)tc.CellElement.Attribute("r")) : null,
+ tc.ColumnAddress != null ? new XAttribute("ColumnId", tc.ColumnAddress) : null,
+ new XAttribute("ColumnNumber", tc.ColumnIndex),
+ type,
+ tc.Formula != null ? new XAttribute("Formula", tc.Formula) : null,
+ tc.Style != null ? new XAttribute("Style", tc.Style) : null,
+ cellProps,
+ new XElement("Value", tc.Value),
+ new XElement("DisplayValue", displayValue),
+ color != null ? new XElement("DisplayColor", color) : null);
+ return newCell;
+ }
+ });
+ var rowProps = GetRowProps(tr.Row.RowElement);
+ var newRow = new XElement("Row",
+ rowProps,
+ new XAttribute("RowNumber", tr.Row.RowId),
+ cellData);
+ return newRow;
+ }));
+
+ XElement tableProps = GetTableProps(table);
+ var tableXml = new XElement("Table",
+ tableProps,
+ table.TableName != null ? new XAttribute("TableName", table.TableName) : null,
+ table.DisplayName != null ? new XAttribute("DisplayName", table.DisplayName) : null,
+ table.Ref != null ? new XAttribute("Ref", table.Ref) : null,
+ table.HeaderRowCount != null ? new XAttribute("HeaderRowCount", table.HeaderRowCount) : null,
+ table.TotalsRowCount != null ? new XAttribute("TotalsRowCount", table.TotalsRowCount) : null,
+ columns,
+ data);
+ return tableXml;
+ }
+
+ public static string[] SheetNames(SmlDocument smlDoc)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ return SheetNames(sDoc);
+ }
+ }
+ }
+
+ public static string[] SheetNames(string fileName)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ return SheetNames(sDoc);
+ }
+ }
+
+ public static string[] SheetNames(SpreadsheetDocument sDoc)
+ {
+ var workbookXDoc = sDoc.WorkbookPart.GetXDocument();
+ var sheetNames = workbookXDoc.Root.Elements(S.sheets).Elements(S.sheet).Attributes("name").Select(a => (string)a).ToArray();
+ return sheetNames;
+ }
+
+ public static string[] TableNames(SmlDocument smlDoc)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ return TableNames(sDoc);
+ }
+ }
+ }
+
+ public static string[] TableNames(string fileName)
+ {
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fileName, false))
+ {
+ return TableNames(sDoc);
+ }
+ }
+
+ public static string[] TableNames(SpreadsheetDocument sDoc)
+ {
+ var workbookXDoc = sDoc.WorkbookPart.GetXDocument();
+ var sheets = workbookXDoc.Root.Elements(S.sheets).Elements(S.sheet);
+ var tableNames = sheets.Select(sh =>
+ {
+ var rId = (string)sh.Attribute(R.id);
+ var sheetPart = sDoc.WorkbookPart.GetPartById(rId);
+ var sheetXDoc = sheetPart.GetXDocument();
+ var tableParts = sheetXDoc.Root.Element(S.tableParts);
+ if (tableParts == null)
+ return new List<string>();
+ var tableNames2 = tableParts
+ .Elements(S.tablePart)
+ .Select(tp =>
+ {
+ var tpRId = (string)tp.Attribute(R.id);
+ var tpart = sheetPart.GetPartById(tpRId);
+ var tpxd = tpart.GetXDocument();
+ var name = (string)tpxd.Root.Attribute("name");
+ return name;
+ })
+ .ToList();
+ return tableNames2;
+ })
+ .SelectMany(m => m)
+ .ToArray();
+ return tableNames;
+ }
+
+ private static XElement GetTableProps(Table table)
+ {
+ XElement tableProps = new XElement("TableProps");
+ XElement tableStyleInfo = table.TableStyleInfo;
+ if (tableStyleInfo != null)
+ {
+ var newTableStyleInfo = TransformRemoveNamespace(tableStyleInfo);
+ tableProps.Add(newTableStyleInfo);
+ }
+
+ if (!tableProps.HasElements && !tableProps.HasElements)
+ tableProps = null;
+ return tableProps;
+ }
+
+ private static XElement GetDataProps(XDocument shXDoc)
+ {
+ var sheetFormatPr = shXDoc.Root.Element(S.sheetFormatPr);
+ if (sheetFormatPr != null && sheetFormatPr.Attribute("defaultColWidth") == null)
+ sheetFormatPr.Add(new XAttribute("defaultColWidth", "9.25"));
+ if (sheetFormatPr != null && sheetFormatPr.Attribute("defaultRowHeight") == null)
+ sheetFormatPr.Add(new XAttribute("defaultRowHeight", "14.25"));
+
+ var mergeCells = TransformRemoveNamespace(shXDoc.Root.Element(S.mergeCells));
+
+ var dataProps = new XElement("DataProps",
+ TransformRemoveNamespace(sheetFormatPr),
+ TransformRemoveNamespace(shXDoc.Root.Element(S.cols)),
+ mergeCells);
+
+ if (!dataProps.HasAttributes && !dataProps.HasElements)
+ dataProps = null;
+ return dataProps;
+ }
+
+ private static XElement GetRowProps(XElement rowElement)
+ {
+ var rowProps = new XElement("RowProps");
+ var ht = rowElement.Attribute("ht");
+ if (ht != null)
+ rowProps.Add(ht);
+ var dyDescent = rowElement.Attribute(x14ac + "dyDescent");
+ if (dyDescent != null)
+ rowProps.Add(new XAttribute("dyDescent", (string)dyDescent));
+
+ if (!rowProps.HasAttributes && !rowProps.HasElements)
+ rowProps = null;
+ return rowProps;
+ }
+
+ private static XElement GetCellProps_NotInTable(SpreadsheetDocument sDoc, XDocument styleXDoc, XElement cell)
+ {
+ var cellProps = new XElement("CellProps");
+ var style = (int?)cell.Attribute("s");
+ if (style == null)
+ return cellProps;
+
+ var xf = styleXDoc
+ .Root
+ .Elements(S.cellXfs)
+ .Elements(S.xf)
+ .Skip((int)style)
+ .FirstOrDefault();
+
+ var numFmtId = (int?)xf.Attribute("numFmtId");
+ if (numFmtId != null)
+ AddNumFmtIdAndFormatCode(styleXDoc, cellProps, numFmtId);
+
+ var masterXfId = (int?)xf.Attribute("xfId");
+ if (masterXfId != null)
+ {
+ var masterXf = styleXDoc
+ .Root
+ .Elements(S.cellStyleXfs)
+ .Elements(S.xf)
+ .Skip((int)masterXfId)
+ .FirstOrDefault();
+ if (masterXf != null)
+ AddFormattingToCellProps(styleXDoc, cellProps, masterXf);
+ }
+
+ AddFormattingToCellProps(styleXDoc, cellProps, xf);
+ AugmentAndCleanUpProps(cellProps);
+
+ if (!cellProps.HasElements && !cellProps.HasAttributes)
+ return null;
+ return cellProps;
+ }
+
+ private static XElement GetCellProps_InTable(SpreadsheetDocument sDoc, XDocument styleXDoc, Table table, Cell tc)
+ {
+ var style = tc.Style;
+ if (style == null)
+ return null;
+
+ var colIdStr = tc.ColumnAddress;
+ int colNbr = XlsxTables.ColumnAddressToIndex(colIdStr);
+ TableColumn column = table.TableColumns().FirstOrDefault(z => z.ColumnNumber == colNbr);
+ if (column == null)
+ throw new FileFormatException("Invalid spreadsheet");
+
+ var cellProps = new XElement("CellProps");
+ var d = column.DataDxfId;
+ if (d != null)
+ {
+ var dataDxf = styleXDoc
+ .Root
+ .Elements(S.dxfs)
+ .Elements(S.dxf)
+ .Skip((int)d)
+ .FirstOrDefault();
+ if (dataDxf == null)
+ throw new FileFormatException("Invalid spreadsheet");
+
+ var numFmt = dataDxf.Element(S.numFmt);
+ if (numFmt != null)
+ {
+ var numFmtId = (int?)numFmt.Attribute("numFmtId");
+ if (numFmtId != null)
+ cellProps.Add(new XAttribute("numFmtId", numFmtId));
+ var formatCode = (string)numFmt.Attribute("formatCode");
+ if (formatCode != null)
+ cellProps.Add(new XAttribute("formatCode", formatCode));
+ }
+ }
+
+ var xf = styleXDoc.Root
+ .Elements(S.cellXfs)
+ .Elements(S.xf)
+ .Skip((int)style)
+ .FirstOrDefault();
+ if (xf == null)
+ throw new FileFormatException("Invalid spreadsheet");
+
+ // if xf has different numFmtId, then replace the ones from the table definition
+ var numFmtId2 = (int?)xf.Attribute("numFmtId");
+ if (numFmtId2 != null)
+ AddNumFmtIdAndFormatCode(styleXDoc, cellProps, numFmtId2);
+
+ var masterXfId = (int?)xf.Attribute("xfId");
+ if (masterXfId != null)
+ {
+ var masterXf = styleXDoc
+ .Root
+ .Elements(S.cellStyleXfs)
+ .Elements(S.xf)
+ .Skip((int)masterXfId)
+ .FirstOrDefault();
+ if (masterXf != null)
+ AddFormattingToCellProps(styleXDoc, cellProps, masterXf);
+ }
+
+ AddFormattingToCellProps(styleXDoc, cellProps, xf);
+ AugmentAndCleanUpProps(cellProps);
+
+ if (!cellProps.HasElements && !cellProps.HasAttributes)
+ return null;
+ return cellProps;
+ }
+
+ private static void AddNumFmtIdAndFormatCode(XDocument styleXDoc, XElement props, int? numFmtId)
+ {
+ var existingNumFmtId = props.Attribute("numFmtId");
+ if (existingNumFmtId != null)
+ existingNumFmtId.Value = numFmtId.ToString();
+ else
+ props.Add(new XAttribute("numFmtId", numFmtId));
+
+ var numFmt = styleXDoc
+ .Root
+ .Elements(S.numFmts)
+ .Elements(S.numFmt)
+ .FirstOrDefault(z => (int)z.Attribute("numFmtId") == numFmtId);
+
+ if (numFmt == null)
+ {
+ var formatCode = GetFormatCodeFromFmtId((int)numFmtId);
+ if (formatCode != null)
+ {
+ var existingFormatCode = props.Attribute("formatCode");
+ if (existingFormatCode != null)
+ existingFormatCode.Value = formatCode;
+ else
+ props.Add(new XAttribute("formatCode", formatCode));
+ }
+ }
+ else
+ {
+ var formatCode = (string)numFmt.Attribute("formatCode");
+ if (formatCode != null)
+ {
+ var existingFormatCode = props.Attribute("formatCode");
+ if (existingFormatCode != null)
+ existingFormatCode.Value = formatCode;
+ else
+ props.Add(new XAttribute("formatCode", formatCode));
+ }
+ }
+ }
+
+ private static void AddFormattingToCellProps(XDocument styleXDoc, XElement props, XElement xf)
+ {
+ MoveBooleanAttribute(props, xf, "applyAlignment");
+ MoveBooleanAttribute(props, xf, "applyBorder");
+ MoveBooleanAttribute(props, xf, "applyFill");
+ MoveBooleanAttribute(props, xf, "applyFont");
+ MoveBooleanAttribute(props, xf, "applyNumberFormat");
+
+ int? borderId = (int?)xf.Attribute("borderId");
+ int? fillId = (int?)xf.Attribute("fillId");
+ int? fontId = (int?)xf.Attribute("fontId");
+
+ if (fontId != null)
+ {
+ var fontElement = styleXDoc
+ .Root
+ .Elements(S.fonts)
+ .Elements(S.font)
+ .Skip((int)fontId)
+ .FirstOrDefault();
+ if (fontElement != null)
+ {
+ var newFontElement = (XElement)TransformRemoveNamespace(fontElement);
+ AddOrReplaceElement(props, newFontElement);
+ }
+ }
+
+ if (fillId != null)
+ {
+ var fillElement = styleXDoc
+ .Root
+ .Elements(S.fills)
+ .Elements(S.fill)
+ .Skip((int)fillId)
+ .FirstOrDefault();
+ if (fillElement != null)
+ {
+ var newFillElement = (XElement)TransformRemoveNamespace(fillElement);
+ AddOrReplaceElement(props, newFillElement);
+ }
+ }
+
+ if (borderId != null)
+ {
+ var borderElement = styleXDoc
+ .Root
+ .Elements(S.borders)
+ .Elements(S.border)
+ .Skip((int)borderId)
+ .FirstOrDefault();
+ if (borderElement != null)
+ {
+ var newborderElement = (XElement)TransformRemoveNamespace(borderElement);
+ AddOrReplaceElement(props, newborderElement);
+ }
+ }
+
+ if (xf.Element(S.alignment) != null)
+ {
+ var newAlignmentElement = (XElement)TransformRemoveNamespace(xf.Element(S.alignment));
+ AddOrReplaceElement(props, newAlignmentElement);
+ }
+ }
+
+ private static void MoveBooleanAttribute(XElement props, XElement xf, XName attributeName)
+ {
+ bool attrValue = ConvertAttributeToBool(xf.Attribute(attributeName));
+ if (attrValue)
+ {
+ if (props.Attribute(attributeName) == null)
+ props.Add(new XAttribute(attributeName, attrValue ? "1" : "0"));
+ else
+ props.Attribute(attributeName).Value = attrValue ? "1" : "0";
+ }
+ }
+
+ public static string[] IndexedColors = new string[] {
+ "00000000",
+ "00FFFFFF",
+ "00FF0000",
+ "0000FF00",
+ "000000FF",
+ "00FFFF00",
+ "00FF00FF",
+ "0000FFFF",
+ "00000000",
+ "00FFFFFF",
+ "00FF0000",
+ "0000FF00",
+ "000000FF",
+ "00FFFF00",
+ "00FF00FF",
+ "0000FFFF",
+ "00800000",
+ "00008000",
+ "00000080",
+ "00808000",
+ "00800080",
+ "00008080",
+ "00C0C0C0",
+ "00808080",
+ "009999FF",
+ "00993366",
+ "00FFFFCC",
+ "00CCFFFF",
+ "00660066",
+ "00FF8080",
+ "000066CC",
+ "00CCCCFF",
+ "00000080",
+ "00FF00FF",
+ "00FFFF00",
+ "0000FFFF",
+ "00800080",
+ "00800000",
+ "00008080",
+ "000000FF",
+ "0000CCFF",
+ "00CCFFFF",
+ "00CCFFCC",
+ "00FFFF99",
+ "0099CCFF",
+ "00FF99CC",
+ "00CC99FF",
+ "00FFCC99",
+ "003366FF",
+ "0033CCCC",
+ "0099CC00",
+ "00FFCC00",
+ "00FF9900",
+ "00FF6600",
+ "00666699",
+ "00969696",
+ "00003366",
+ "00339966",
+ "00003300",
+ "00333300",
+ "00993300",
+ "00993366",
+ "00333399",
+ "00333333",
+ "System Foreground",
+ "System Background",
+ };
+
+ private static string[] FontFamilyList = new string[] {
+ "Not applicable",
+ "Roman",
+ "Swiss",
+ "Modern",
+ "Script",
+ "Decorative",
+ };
+
+ private static void AugmentAndCleanUpProps(XElement props)
+ {
+ foreach (var color in props.Descendants("color").Where(c => c.Attribute("indexed") != null).ToList())
+ {
+ var idx = (int)color.Attribute("indexed");
+ if (idx < IndexedColors.Length)
+ {
+ color.Add(new XAttribute("val", IndexedColors[idx]));
+ }
+ color.Attribute("indexed").Remove();
+ }
+ foreach (var family in props.Descendants("family").ToList())
+ {
+ var fam = (int?)family.Attribute("val");
+ if (fam != null)
+ {
+ if (fam < FontFamilyList.Length)
+ {
+ family.Attribute("val").Remove();
+ family.Add(new XAttribute("val", FontFamilyList[(int)fam]));
+ }
+ }
+ }
+ foreach (var border in props.Descendants("border").ToList())
+ {
+ RemoveIfEmpty(border.Element("left"));
+ RemoveIfEmpty(border.Element("right"));
+ RemoveIfEmpty(border.Element("top"));
+ RemoveIfEmpty(border.Element("bottom"));
+ RemoveIfEmpty(border.Element("diagonal"));
+ if (!border.HasAttributes && !border.HasElements)
+ border.Remove();
+ }
+ foreach (var fill in props.Descendants("fill").ToList())
+ {
+ fill.Elements("patternFill").Where(pf => (string)pf.Attribute("patternType") == "none").Remove();
+ if (!fill.HasAttributes && !fill.HasElements)
+ fill.Remove();
+ }
+ }
+
+ private static void RemoveIfEmpty(XElement xElement)
+ {
+ if (xElement == null)
+ return;
+ if (!xElement.HasAttributes && !xElement.HasElements)
+ xElement.Remove();
+ }
+
+
+ private static object TransformRemoveNamespace(XNode node)
+ {
+ if (node == null)
+ return null;
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name.LocalName,
+ element.Attributes().Select(a => new XAttribute(a.Name.LocalName, (string)a)).OrderBy(a => (string)a.Name.LocalName),
+ element.Nodes().Select(n => TransformRemoveNamespace(n)));
+ }
+ return node;
+ }
+
+ private static string GetFormatCodeFromFmtId(int fmtId)
+ {
+ switch (fmtId)
+ {
+ case 0: return "General";
+ case 1: return "0";
+ case 2: return "0.00";
+ case 3: return "#,##0";
+ case 4: return "#,##0.00";
+ case 9: return "0%";
+ case 10: return "0.00%";
+ case 11: return "0.00E+00";
+ case 12: return "# ?/?";
+ case 13: return "# ??/??";
+ case 14: return "mm-dd-yy";
+ case 15: return "d-mmm-yy";
+ case 16: return "d-mmm";
+ case 17: return "mmm-yy";
+ case 18: return "h:mm AM/PM";
+ case 19: return "h:mm:ss AM/PM";
+ case 20: return "h:mm";
+ case 21: return "h:mm:ss";
+ case 22: return "22 m/d/yy h:mm";
+ case 37: return "#,##0 ;(#,##0)";
+ case 38: return "#,##0 ;[Red](#,##0)";
+ case 39: return "#,##0.00;(#,##0.00)";
+ case 40: return "#,##0.00;[Red](#,##0.00)";
+ case 45: return "mm:ss";
+ case 46: return "[h]:mm:ss";
+ case 47: return "mmss.0";
+ case 48: return "##0.0E+0";
+ case 49: return "@";
+ default: return null;
+ }
+ }
+
+ private static void AddOrReplaceElement(XElement props, XName childElementName, int value)
+ {
+ var existingElement = props.Element(childElementName);
+ if (existingElement != null)
+ existingElement.ReplaceWith(new XElement(childElementName, new XAttribute("Val", value)));
+ else
+ props.Add(new XElement(childElementName, new XAttribute("Val", value)));
+ }
+
+ private static void AddOrReplaceElement(XElement props, XName childElementName, string value)
+ {
+ var existingElement = props.Element(childElementName);
+ if (existingElement != null)
+ existingElement.ReplaceWith(new XElement(childElementName, new XAttribute("Val", value)));
+ else
+ props.Add(new XElement(childElementName, new XAttribute("Val", value)));
+ }
+
+ private static void AddOrReplaceElement(XElement props, XElement element)
+ {
+ var existingElement = props.Element(element.Name);
+ if (existingElement != null)
+ existingElement.ReplaceWith(element);
+ else
+ props.Add(element);
+ }
+
+ private static bool ConvertAttributeToBool(XAttribute xAttribute)
+ {
+ string applyNumberFormatStr = (string)xAttribute;
+ bool returnValue = false;
+ if (applyNumberFormatStr != null)
+ {
+ if (applyNumberFormatStr == "1")
+ returnValue = true;
+ if (applyNumberFormatStr.Substring(0, 1).ToUpper() == "T")
+ returnValue = true;
+ }
+ return returnValue;
+ }
+
+ private static XNamespace x14ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac";
+ }
+}
diff --git a/OpenXmlPowerTools/SmlToHtmlConverter.cs b/OpenXmlPowerTools/SmlToHtmlConverter.cs
new file mode 100644
index 0000000..f2f54d7
--- /dev/null
+++ b/OpenXmlPowerTools/SmlToHtmlConverter.cs
@@ -0,0 +1,324 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.IO;
+
+namespace OpenXmlPowerTools
+{
+ public partial class SmlDocument
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public XElement ConvertToHtml(SmlToHtmlConverterSettings htmlConverterSettings, string tableName)
+ {
+ return SmlToHtmlConverter.ConvertTableToHtml(this, htmlConverterSettings, tableName);
+ }
+
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public XElement ConvertTableToHtml(string tableName)
+ {
+ SmlToHtmlConverterSettings settings = new SmlToHtmlConverterSettings();
+ return SmlToHtmlConverter.ConvertTableToHtml(this, settings, tableName);
+ }
+ }
+
+ [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
+ public class SmlToHtmlConverterSettings
+ {
+ public string PageTitle;
+ public string CssClassPrefix;
+ public bool FabricateCssClasses;
+ public string GeneralCss;
+ public string AdditionalCss;
+
+ public SmlToHtmlConverterSettings()
+ {
+ PageTitle = "";
+ CssClassPrefix = "pt-";
+ FabricateCssClasses = true;
+ GeneralCss = "span { white-space: pre-wrap; }";
+ AdditionalCss = "";
+ }
+
+ public SmlToHtmlConverterSettings(SmlToHtmlConverterSettings htmlConverterSettings)
+ {
+ PageTitle = htmlConverterSettings.PageTitle;
+ CssClassPrefix = htmlConverterSettings.CssClassPrefix;
+ FabricateCssClasses = htmlConverterSettings.FabricateCssClasses;
+ GeneralCss = htmlConverterSettings.GeneralCss;
+ AdditionalCss = htmlConverterSettings.AdditionalCss;
+ }
+ }
+
+ public static class SmlToHtmlConverter
+ {
+ // ***********************************************************************************************************************************
+ #region PublicApis
+ public static XElement ConvertTableToHtml(SmlDocument smlDoc, SmlToHtmlConverterSettings settings, string tableName)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(smlDoc.DocumentByteArray, 0, smlDoc.DocumentByteArray.Length);
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(ms, false))
+ {
+ var rangeXml = SmlDataRetriever.RetrieveTable(sDoc, tableName);
+ var xhtml = SmlToHtmlConverter.ConvertToHtmlInternal(sDoc, settings, rangeXml);
+ return xhtml;
+ }
+ }
+ }
+
+ public static XElement ConvertTableToHtml(SpreadsheetDocument sDoc, SmlToHtmlConverterSettings settings, string tableName)
+ {
+ var rangeXml = SmlDataRetriever.RetrieveTable(sDoc, tableName);
+ var xhtml = SmlToHtmlConverter.ConvertToHtmlInternal(sDoc, settings, rangeXml);
+ return xhtml;
+ }
+ #endregion
+ // ***********************************************************************************************************************************
+
+ private static XElement ConvertToHtmlInternal(SpreadsheetDocument sDoc, SmlToHtmlConverterSettings htmlConverterSettings, XElement rangeXml)
+ {
+ XElement xhtml = (XElement)ConvertToHtmlTransform(sDoc, htmlConverterSettings, rangeXml);
+
+ ReifyStylesAndClasses(htmlConverterSettings, xhtml);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ return xhtml;
+ }
+
+ private static XNode ConvertToHtmlTransform(SpreadsheetDocument sDoc, SmlToHtmlConverterSettings htmlConverterSettings, XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => ConvertToHtmlTransform(sDoc, htmlConverterSettings, n)));
+ }
+ return node;
+ }
+
+ private static void ReifyStylesAndClasses(SmlToHtmlConverterSettings htmlConverterSettings, XElement xhtml)
+ {
+ if (htmlConverterSettings.FabricateCssClasses)
+ {
+ var usedCssClassNames = new HashSet<string>();
+ var elementsThatNeedClasses = xhtml
+ .DescendantsAndSelf()
+ .Select(d => new
+ {
+ Element = d,
+ Styles = d.Annotation<Dictionary<string, string>>(),
+ })
+ .Where(z => z.Styles != null);
+ var augmented = elementsThatNeedClasses
+ .Select(p => new
+ {
+ p.Element,
+ p.Styles,
+ StylesString = p.Element.Name.LocalName + "|" + p.Styles.OrderBy(k => k.Key).Select(s => string.Format("{0}: {1};", s.Key, s.Value)).StringConcatenate(),
+ })
+ .GroupBy(p => p.StylesString)
+ .ToList();
+ int classCounter = 1000000;
+ var sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ foreach (var grp in augmented)
+ {
+ string classNameToUse;
+ var firstOne = grp.First();
+ var styles = firstOne.Styles;
+ if (styles.ContainsKey("PtStyleName"))
+ {
+ classNameToUse = htmlConverterSettings.CssClassPrefix + styles["PtStyleName"];
+ if (usedCssClassNames.Contains(classNameToUse))
+ {
+ classNameToUse = htmlConverterSettings.CssClassPrefix +
+ styles["PtStyleName"] + "-" +
+ classCounter.ToString().Substring(1);
+ classCounter++;
+ }
+ }
+ else
+ {
+ classNameToUse = htmlConverterSettings.CssClassPrefix +
+ classCounter.ToString().Substring(1);
+ classCounter++;
+ }
+ usedCssClassNames.Add(classNameToUse);
+ sb.Append(firstOne.Element.Name.LocalName + "." + classNameToUse + " {" + Environment.NewLine);
+ foreach (var st in firstOne.Styles.Where(s => s.Key != "PtStyleName"))
+ {
+ var s = " " + st.Key + ": " + st.Value + ";" + Environment.NewLine;
+ sb.Append(s);
+ }
+ sb.Append("}" + Environment.NewLine);
+ var classAtt = new XAttribute("class", classNameToUse);
+ foreach (var gc in grp)
+ gc.Element.Add(classAtt);
+ }
+ var styleValue = htmlConverterSettings.GeneralCss + sb + htmlConverterSettings.AdditionalCss;
+
+ SetStyleElementValue(xhtml, styleValue);
+ }
+ else
+ {
+ // Previously, the h:style element was not added at this point. However,
+ // at least the General CSS will contain important settings.
+ SetStyleElementValue(xhtml, htmlConverterSettings.GeneralCss + htmlConverterSettings.AdditionalCss);
+
+ foreach (var d in xhtml.DescendantsAndSelf())
+ {
+ var style = d.Annotation<Dictionary<string, string>>();
+ if (style == null)
+ continue;
+ var styleValue =
+ style
+ .Where(p => p.Key != "PtStyleName")
+ .OrderBy(p => p.Key)
+ .Select(e => string.Format("{0}: {1};", e.Key, e.Value))
+ .StringConcatenate();
+ XAttribute st = new XAttribute("style", styleValue);
+ if (d.Attribute("style") != null)
+ d.Attribute("style").Value += styleValue;
+ else
+ d.Add(st);
+ }
+ }
+ }
+
+ private static void SetStyleElementValue(XElement xhtml, string styleValue)
+ {
+ var styleElement = xhtml
+ .Descendants(Xhtml.style)
+ .FirstOrDefault();
+ if (styleElement != null)
+ styleElement.Value = styleValue;
+ else
+ {
+ styleElement = new XElement(Xhtml.style, styleValue);
+ var head = xhtml.Element(Xhtml.head);
+ if (head != null)
+ head.Add(styleElement);
+ }
+ }
+
+ private static object ConvertToHtmlTransform(WordprocessingDocument wordDoc,
+ WmlToHtmlConverterSettings settings, XNode node)
+ {
+ // Ignore element.
+ return null;
+ }
+
+ private static readonly HashSet<string> UnknownFonts = new HashSet<string>();
+ private static HashSet<string> _knownFamilies;
+
+ private static HashSet<string> KnownFamilies
+ {
+ get
+ {
+ if (_knownFamilies == null)
+ {
+ _knownFamilies = new HashSet<string>();
+ var families = FontFamily.Families;
+ foreach (var fam in families)
+ _knownFamilies.Add(fam.Name);
+ }
+ return _knownFamilies;
+ }
+ }
+
+ private static readonly Dictionary<string, string> FontFallback = new Dictionary<string, string>()
+ {
+ { "Arial", @"'{0}', 'sans-serif'" },
+ { "Arial Narrow", @"'{0}', 'sans-serif'" },
+ { "Arial Rounded MT Bold", @"'{0}', 'sans-serif'" },
+ { "Arial Unicode MS", @"'{0}', 'sans-serif'" },
+ { "Baskerville Old Face", @"'{0}', 'serif'" },
+ { "Berlin Sans FB", @"'{0}', 'sans-serif'" },
+ { "Berlin Sans FB Demi", @"'{0}', 'sans-serif'" },
+ { "Calibri Light", @"'{0}', 'sans-serif'" },
+ { "Gill Sans MT", @"'{0}', 'sans-serif'" },
+ { "Gill Sans MT Condensed", @"'{0}', 'sans-serif'" },
+ { "Lucida Sans", @"'{0}', 'sans-serif'" },
+ { "Lucida Sans Unicode", @"'{0}', 'sans-serif'" },
+ { "Segoe UI", @"'{0}', 'sans-serif'" },
+ { "Segoe UI Light", @"'{0}', 'sans-serif'" },
+ { "Segoe UI Semibold", @"'{0}', 'sans-serif'" },
+ { "Tahoma", @"'{0}', 'sans-serif'" },
+ { "Trebuchet MS", @"'{0}', 'sans-serif'" },
+ { "Verdana", @"'{0}', 'sans-serif'" },
+ { "Book Antiqua", @"'{0}', 'serif'" },
+ { "Bookman Old Style", @"'{0}', 'serif'" },
+ { "Californian FB", @"'{0}', 'serif'" },
+ { "Cambria", @"'{0}', 'serif'" },
+ { "Constantia", @"'{0}', 'serif'" },
+ { "Garamond", @"'{0}', 'serif'" },
+ { "Lucida Bright", @"'{0}', 'serif'" },
+ { "Lucida Fax", @"'{0}', 'serif'" },
+ { "Palatino Linotype", @"'{0}', 'serif'" },
+ { "Times New Roman", @"'{0}', 'serif'" },
+ { "Wide Latin", @"'{0}', 'serif'" },
+ { "Courier New", @"'{0}'" },
+ { "Lucida Console", @"'{0}'" },
+ };
+
+ private static void CreateFontCssProperty(string font, Dictionary<string, string> style)
+ {
+ if (FontFallback.ContainsKey(font))
+ {
+ style.AddIfMissing("font-family", string.Format(FontFallback[font], font));
+ return;
+ }
+ style.AddIfMissing("font-family", font);
+ }
+
+ private static bool GetBoolProp(XElement runProps, XName xName)
+ {
+ var p = runProps.Element(xName);
+ if (p == null)
+ return false;
+ var v = p.Attribute(W.val);
+ if (v == null)
+ return true;
+ var s = v.Value.ToLower();
+ if (s == "0" || s == "false")
+ return false;
+ if (s == "1" || s == "true")
+ return true;
+ return false;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/SpreadsheetDocumentManager.cs b/OpenXmlPowerTools/SpreadsheetDocumentManager.cs
new file mode 100644
index 0000000..b1f6bef
--- /dev/null
+++ b/OpenXmlPowerTools/SpreadsheetDocumentManager.cs
@@ -0,0 +1,223 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ /// <summary>
+ /// Manages SpreadsheetDocument content
+ /// </summary>
+ public class SpreadsheetDocumentManager
+ {
+ private static XNamespace ns;
+ private static XNamespace relationshipsns;
+ private static int headerRow = 1;
+
+ static SpreadsheetDocumentManager()
+ {
+ ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ relationshipsns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
+ }
+
+ /// <summary>
+ /// Creates a spreadsheet document from a value table
+ /// </summary>
+ /// <param name="filePath">Path to store the document</param>
+ /// <param name="headerList">Contents of first row (header)</param>
+ /// <param name="valueTable">Contents of data</param>
+ /// <param name="initialRow">Row to start copying data from</param>
+ /// <returns></returns>
+ public static void Create(SpreadsheetDocument document, List<string> headerList, string[][] valueTable, int initialRow)
+ {
+ headerRow = initialRow;
+
+ //Creates a worksheet with given data
+ WorksheetPart worksheet = WorksheetAccessor.Create(document, headerList, valueTable, headerRow);
+ }
+
+ /// <summary>
+ /// Creates a spreadsheet document with a chart from a value table
+ /// </summary>
+ /// <param name="filePath">Path to store the document</param>
+ /// <param name="headerList">Contents of first row (header)</param>
+ /// <param name="valueTable">Contents of data</param>
+ /// <param name="chartType">Chart type</param>
+ /// <param name="categoryColumn">Column to use as category for charting</param>
+ /// <param name="columnsToChart">Columns to use as data series</param>
+ /// <param name="initialRow">Row index to start copying data</param>
+ /// <returns>SpreadsheetDocument</returns>
+ //public static void Create(SpreadsheetDocument document, List<string> headerList, string[][] valueTable, ChartType chartType, string categoryColumn, List<string> columnsToChart, int initialRow)
+ //{
+ // headerRow = initialRow;
+
+ // //Creates worksheet with data
+ // WorksheetPart worksheet = WorksheetAccessor.Create(document, headerList, valueTable, headerRow);
+ // //Creates chartsheet with given series and category
+ // string sheetName = GetSheetName(worksheet, document);
+ // ChartsheetPart chartsheet =
+ // ChartsheetAccessor.Create(document,
+ // chartType,
+ // GetValueReferences(sheetName, categoryColumn, headerList, columnsToChart, valueTable),
+ // GetHeaderReferences(sheetName, categoryColumn, headerList, columnsToChart, valueTable),
+ // GetCategoryReference(sheetName, categoryColumn, headerList, valueTable)
+ // );
+ //}
+
+ /// <summary>
+ /// Gets the internal name of a worksheet from a document
+ /// </summary>
+ private static string GetSheetName(WorksheetPart worksheet, SpreadsheetDocument document)
+ {
+ //Gets the id of worksheet part
+ string partId = document.WorkbookPart.GetIdOfPart(worksheet);
+ XDocument workbookDocument = document.WorkbookPart.GetXDocument();
+ //Gets the name from sheet tag related to worksheet
+ string sheetName =
+ workbookDocument.Root
+ .Element(ns + "sheets")
+ .Elements(ns + "sheet")
+ .Where(
+ t =>
+ t.Attribute(relationshipsns + "id").Value == partId
+ ).First()
+ .Attribute("name").Value;
+ return sheetName;
+ }
+ /// <summary>
+ /// Gets the range reference for category
+ /// </summary>
+ /// <param name="sheetName">worksheet to take data from</param>
+ /// <param name="headerColumn">name of column used as category</param>
+ /// <param name="headerList">column names from data</param>
+ /// <param name="valueTable">Data values</param>
+ /// <returns></returns>
+ private static string GetCategoryReference(string sheetName, string headerColumn, List<string> headerList, string[][] valueTable)
+ {
+ int categoryColumn = headerList.IndexOf(headerColumn.ToUpper()) + 1;
+ int numRows = valueTable.GetLength(0);
+
+ return GetRangeReference(
+ sheetName,
+ categoryColumn,
+ headerRow + 1,
+ categoryColumn,
+ numRows + headerRow
+ );
+ }
+
+ /// <summary>
+ /// Gets a list of range references for each of the series headers
+ /// </summary>
+ /// <param name="sheetName">worksheet to take data from</param>
+ /// <param name="headerColumn">name of column used as category</param>
+ /// <param name="headerList">column names from data</param>
+ /// <param name="valueTable">Data values</param>
+ /// <param name="colsToChart">Columns used as data series</param>
+ /// <returns></returns>
+ private static List<string> GetHeaderReferences(string sheetName, string headerColumn, List<string> headerList, List<string> colsToChart, string[][] valueTable)
+ {
+ List<string> valueReferenceList = new List<string>();
+
+ foreach (string column in colsToChart)
+ {
+ valueReferenceList.Add(
+ GetRangeReference(
+ sheetName,
+ headerList.IndexOf(column.ToUpper()) + 1,
+ headerRow
+ )
+ );
+ }
+ return valueReferenceList;
+ }
+
+ /// <summary>
+ /// Gets a list of range references for each of the series values
+ /// </summary>
+ /// <param name="sheetName">worksheet to take data from</param>
+ /// <param name="headerColumn">name of column used as category</param>
+ /// <param name="headerList">column names from data</param>
+ /// <param name="valueTable">Data values</param>
+ /// <param name="colsToChart">Columns used as data series</param>
+ /// <returns></returns>
+ private static List<string> GetValueReferences(string sheetName, string headerColumn, List<string> headerList, List<string> colsToChart, string[][] valueTable)
+ {
+ List<string> valueReferenceList = new List<string>();
+ int numRows = valueTable.GetLength(0);
+
+ foreach (string column in colsToChart)
+ {
+ int dataColumn = headerList.IndexOf(column.ToUpper()) + 1;
+ valueReferenceList.Add(
+ GetRangeReference(
+ sheetName,
+ dataColumn,
+ headerRow + 1,
+ dataColumn,
+ numRows + headerRow
+ )
+ );
+ }
+ return valueReferenceList;
+ }
+
+ /// <summary>
+ /// Gets a formatted representation of a cell range from a worksheet
+ /// </summary>
+ private static string GetRangeReference(string worksheet, int column, int row)
+ {
+ return string.Format("{0}!{1}{2}", worksheet, WorksheetAccessor.GetColumnId(column), row);
+ }
+
+ /// <summary>
+ /// Gets a formatted representation of a cell range from a worksheet
+ /// </summary>
+ private static string GetRangeReference(string worksheet, int startColumn, int startRow, int endColumn, int endRow)
+ {
+ return string.Format("{0}!{1}{2}:{3}{4}",
+ worksheet,
+ WorksheetAccessor.GetColumnId(startColumn),
+ startRow,
+ WorksheetAccessor.GetColumnId(endColumn),
+ endRow
+ );
+ }
+
+ /// <summary>
+ /// Creates an empty (base) workbook document
+ /// </summary>
+ /// <returns></returns>
+ private static XDocument CreateEmptyWorkbook()
+ {
+ XDocument document =
+ new XDocument(
+ new XElement(ns + "workbook",
+ new XAttribute("xmlns", ns),
+ new XAttribute(XNamespace.Xmlns + "r", relationshipsns),
+ new XElement(ns + "sheets")
+ )
+ );
+
+ return document;
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenXmlPowerTools/SpreadsheetWriter.cs b/OpenXmlPowerTools/SpreadsheetWriter.cs
new file mode 100644
index 0000000..e9122ab
--- /dev/null
+++ b/OpenXmlPowerTools/SpreadsheetWriter.cs
@@ -0,0 +1,796 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+#undef DisplayWorkingSet
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ // The classes in SpreadsheetWriter are still a work-in-progress. While they are useful in their current state, I will be enhancing and
+ // changing them in the future. In particular, I will be augmenting the various definition classes (WorkbookDfn, WorksheetDfn,
+ // RowDfn, and CellDfn.
+
+ // They are robust enough in their current form to be used in enterprise, mission critical.
+
+ public class WorkbookDfn
+ {
+ public IEnumerable<WorksheetDfn> Worksheets;
+ }
+
+ public class WorksheetDfn
+ {
+ public string Name;
+ public string TableName;
+ public IEnumerable<CellDfn> ColumnHeadings;
+ public IEnumerable<RowDfn> Rows;
+ }
+
+ public class RowDfn
+ {
+ public IEnumerable<CellDfn> Cells;
+ }
+
+ // Value can be:
+ // - string
+ // - bool
+ // - DateTime
+ // - int32, int64, uint, double, float, etc.
+
+ // Standard formats
+ public class CellDfn
+ {
+ public static Dictionary<string, int> StandardFormats = new Dictionary<string, int>
+ {
+ { "0", 1 },
+ { "0.00", 2 },
+ { "#,##0", 3 },
+ { "#,##0.00", 4 },
+ { "0%", 9 },
+ { "0.00%", 10 },
+ { "0.00E+00", 11 },
+ { "# ?/?", 12 },
+ { "# ??/??", 13 },
+ { "mm-dd-yy", 14 },
+ { "d-mmm-yy", 15 },
+ { "d-mmm", 16 },
+ { "mmm-yy", 17 },
+ { "h:mm AM/PM", 18 },
+ { "h:mm:ss AM/PM", 19 },
+ { "h:mm", 20 },
+ { "h:mm:ss", 21 },
+ { "h/d/yy h:mm", 22 },
+ { "#,##0;(#,##0)", 37 },
+ { "#,##0;[Red](#,##0)", 38 },
+ { "#,##0.00;(#,##0.00)", 39 },
+ { "#,##0.00;[Red](#,##0.00)", 40 },
+ { "mm:ss", 45 },
+ { "[h]:mm:ss", 46 },
+ { "mmss.0", 47 },
+ { "##0.0E+0", 48 },
+ { "@", 49 },
+ };
+ public object Value;
+ public CellDataType? CellDataType;
+ public HorizontalCellAlignment? HorizontalCellAlignment;
+ public bool? Bold;
+ public bool? Italic;
+ public string FormatCode;
+ }
+
+ public enum HorizontalCellAlignment
+ {
+ Left,
+ Center,
+ Right,
+ }
+
+ public enum CellDataType
+ {
+ Boolean,
+ Date,
+ Number,
+ String,
+ }
+
+ public static class SpreadsheetWriter
+ {
+ public static void Write(string fileName, WorkbookDfn workbook)
+ {
+ try
+ {
+ if (fileName == null) throw new ArgumentNullException("fileName");
+ if (workbook == null) throw new ArgumentNullException("workbook");
+
+ FileInfo fi = new FileInfo(fileName);
+ if (fi.Exists)
+ fi.Delete();
+
+ // create the blank workbook
+ char[] base64CharArray = _EmptyXlsx
+ .Where(c => c != '\r' && c != '\n').ToArray();
+ byte[] byteArray =
+ System.Convert.FromBase64CharArray(base64CharArray,
+ 0, base64CharArray.Length);
+ File.WriteAllBytes(fi.FullName, byteArray);
+
+ // open the workbook, and create the TableProperties sheet, populate it
+ using (SpreadsheetDocument sDoc = SpreadsheetDocument.Open(fi.FullName, true))
+ {
+ WorkbookPart workbookPart = sDoc.WorkbookPart;
+ XDocument wXDoc = workbookPart.GetXDocument();
+ XElement sheetElement = wXDoc
+ .Root
+ .Elements(S.sheets)
+ .Elements(S.sheet)
+ .Where(s => (string)s.Attribute(SSNoNamespace.name) == "Sheet1")
+ .FirstOrDefault();
+ if (sheetElement == null)
+ throw new SpreadsheetWriterInternalException();
+ string id = (string)sheetElement.Attribute(R.id);
+ sheetElement.Remove();
+ workbookPart.PutXDocument();
+
+ WorksheetPart sPart = (WorksheetPart)workbookPart.GetPartById(id);
+ workbookPart.DeletePart(sPart);
+
+ XDocument appXDoc = sDoc
+ .ExtendedFilePropertiesPart
+ .GetXDocument();
+ XElement vector = appXDoc
+ .Root
+ .Elements(EP.TitlesOfParts)
+ .Elements(VT.vector)
+ .FirstOrDefault();
+ if (vector != null)
+ {
+ vector.SetAttributeValue(SSNoNamespace.size, 0);
+ XElement lpstr = vector.Element(VT.lpstr);
+ lpstr.Remove();
+ }
+ XElement vector2 = appXDoc
+ .Root
+ .Elements(EP.HeadingPairs)
+ .Elements(VT.vector)
+ .FirstOrDefault();
+ XElement variant = vector2
+ .Descendants(VT.i4)
+ .FirstOrDefault();
+ if (variant != null)
+ variant.Value = "1";
+ sDoc.ExtendedFilePropertiesPart.PutXDocument();
+
+ if (workbook.Worksheets != null)
+ foreach (var worksheet in workbook.Worksheets)
+ AddWorksheet(sDoc, worksheet);
+
+ workbookPart.WorkbookStylesPart.PutXDocument();
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Unhandled exception: {0} in {1}",
+ e.ToString(), e.Source);
+ throw e;
+ }
+ }
+
+ public static void AddWorksheet(SpreadsheetDocument sDoc, WorksheetDfn worksheetData)
+ {
+ Regex validSheetName = new Regex(@"^[^'*\[\]/\\:?][^*\[\]/\\:?]{0,30}$");
+ if (!validSheetName.IsMatch(worksheetData.Name))
+ throw new InvalidSheetNameException(worksheetData.Name);
+
+ // throw WorksheetAlreadyExistsException if a sheet with the same name (case-insensitive) already exists in the workbook
+ string UCName = worksheetData.Name.ToUpper();
+ XDocument wXDoc = sDoc.WorkbookPart.GetXDocument();
+ if (wXDoc
+ .Root
+ .Elements(S.sheets)
+ .Elements(S.sheet)
+ .Attributes(SSNoNamespace.name)
+ .Select(a => ((string)a).ToUpper())
+ .Contains(UCName))
+ throw new WorksheetAlreadyExistsException(worksheetData.Name);
+
+ // create the worksheet with the supplied name
+ XDocument appXDoc = sDoc
+ .ExtendedFilePropertiesPart
+ .GetXDocument();
+ XElement vector = appXDoc
+ .Root
+ .Elements(EP.TitlesOfParts)
+ .Elements(VT.vector)
+ .FirstOrDefault();
+ if (vector != null)
+ {
+ int? size = (int?)vector.Attribute(SSNoNamespace.size);
+ if (size == null)
+ size = 1;
+ else
+ size = size + 1;
+ vector.SetAttributeValue(SSNoNamespace.size, size);
+ vector.Add(
+ new XElement(VT.lpstr, worksheetData.Name));
+ XElement i4 = appXDoc
+ .Root
+ .Elements(EP.HeadingPairs)
+ .Elements(VT.vector)
+ .Elements(VT.variant)
+ .Elements(VT.i4)
+ .FirstOrDefault();
+ if (i4 != null)
+ i4.Value = ((int)i4 + 1).ToString();
+ sDoc.ExtendedFilePropertiesPart.PutXDocument();
+ }
+
+ WorkbookPart workbook = sDoc.WorkbookPart;
+ string rId = "R" + Guid.NewGuid().ToString().Replace("-", "");
+ WorksheetPart worksheetPart = workbook.AddNewPart<WorksheetPart>(rId);
+
+ XDocument wbXDoc = workbook.GetXDocument();
+ XElement sheets = wbXDoc.Descendants(S.sheets).FirstOrDefault();
+ sheets.Add(
+ new XElement(S.sheet,
+ new XAttribute(SSNoNamespace.name, worksheetData.Name.ToString()),
+ new XAttribute(SSNoNamespace.sheetId, sheets.Elements(S.sheet).Count() + 1),
+ new XAttribute(R.id, rId)));
+ workbook.PutXDocument();
+
+ string ws = S.s.ToString();
+ string relns = R.r.ToString();
+
+ using (Stream partStream = worksheetPart.GetStream(FileMode.Create, FileAccess.Write))
+ {
+ using (XmlWriter partXmlWriter = XmlWriter.Create(partStream))
+ {
+ partXmlWriter.WriteStartDocument();
+ partXmlWriter.WriteStartElement("worksheet", ws);
+ partXmlWriter.WriteStartElement("sheetData", ws);
+
+ int numColumnHeadingRows = 0;
+ int numColumns = 0;
+ int numColumnsInRows = 0;
+ int numRows;
+ if (worksheetData.ColumnHeadings != null)
+ {
+ RowDfn row = new RowDfn
+ {
+ Cells = worksheetData.ColumnHeadings
+ };
+ SerializeRows(sDoc, partXmlWriter, new[] { row }, 1, out numColumns, out numColumnHeadingRows);
+ }
+ SerializeRows(sDoc, partXmlWriter, worksheetData.Rows, numColumnHeadingRows + 1, out numColumnsInRows,
+ out numRows);
+ int totalRows = numColumnHeadingRows + numRows;
+ int totalColumns = Math.Max(numColumns, numColumnsInRows);
+ if (worksheetData.ColumnHeadings != null && worksheetData.TableName != null)
+ {
+ partXmlWriter.WriteEndElement();
+ string rId2 = "R" + Guid.NewGuid().ToString().Replace("-", "");
+ partXmlWriter.WriteStartElement("tableParts", ws);
+ partXmlWriter.WriteStartAttribute("count");
+ partXmlWriter.WriteValue(1);
+ partXmlWriter.WriteEndAttribute();
+ partXmlWriter.WriteStartElement("tablePart", ws);
+ partXmlWriter.WriteStartAttribute("id", relns);
+ partXmlWriter.WriteValue(rId2);
+ TableDefinitionPart tdp = worksheetPart.AddNewPart<TableDefinitionPart>(rId2);
+ XDocument tXDoc = tdp.GetXDocument();
+ XElement table = new XElement(S.table,
+ new XAttribute(SSNoNamespace.id, 1),
+ new XAttribute(SSNoNamespace.name, worksheetData.TableName),
+ new XAttribute(SSNoNamespace.displayName, worksheetData.TableName),
+ new XAttribute(SSNoNamespace._ref, "A1:" + SpreadsheetMLUtil.IntToColumnId(totalColumns - 1) + totalRows.ToString()),
+ new XAttribute(SSNoNamespace.totalsRowShown, 0),
+ new XElement(S.autoFilter,
+ new XAttribute(SSNoNamespace._ref, "A1:" + SpreadsheetMLUtil.IntToColumnId(totalColumns - 1) + totalRows.ToString())),
+ new XElement(S.tableColumns,
+ new XAttribute(SSNoNamespace.count, totalColumns),
+ worksheetData.ColumnHeadings.Select((ch, i) =>
+ new XElement(S.tableColumn,
+ new XAttribute(SSNoNamespace.id, i + 1),
+ new XAttribute(SSNoNamespace.name, ch.Value)))),
+ new XElement(S.tableStyleInfo,
+ new XAttribute(SSNoNamespace.name, "TableStyleMedium2"),
+ new XAttribute(SSNoNamespace.showFirstColumn, 0),
+ new XAttribute(SSNoNamespace.showLastColumn, 0),
+ new XAttribute(SSNoNamespace.showRowStripes, 1),
+ new XAttribute(SSNoNamespace.showColumnStripes, 0)));
+ tXDoc.Add(table);
+ tdp.PutXDocument();
+ }
+ }
+ }
+ sDoc.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ sDoc.WorkbookPart.WorkbookStylesPart.Stylesheet.Save();
+ }
+
+ private static void SerializeRows(SpreadsheetDocument sDoc, XmlWriter xmlWriter, IEnumerable<RowDfn> rows,
+ int startingRowNumber, out int numColumns, out int numRows)
+ {
+ int rowCount = 0;
+ int rowNumber = startingRowNumber;
+ int maxColumns = 0;
+ int localNumColumns;
+#if DisplayWorkingSet
+ int workingSetInterval = 10000;
+ int workingSetCount = 0;
+#endif
+ foreach (var row in rows)
+ {
+ SerializeRow(sDoc, xmlWriter, rowNumber, row, out localNumColumns);
+ maxColumns = Math.Max(maxColumns, localNumColumns);
+ rowNumber++;
+ rowCount++;
+#if DisplayWorkingSet
+ if (workingSetCount++ > workingSetInterval)
+ {
+ workingSetCount = 0;
+ Console.WriteLine(Environment.WorkingSet);
+ }
+#endif
+ }
+ numColumns = maxColumns;
+ numRows = rowCount;
+ }
+
+ private static void SerializeRow(SpreadsheetDocument sDoc, XmlWriter xw, int rowCount, RowDfn row, out int numColumns)
+ {
+ string ns = S.s.NamespaceName;
+
+ xw.WriteStartElement("row", ns);
+ xw.WriteStartAttribute("r");
+ xw.WriteValue(rowCount);
+ xw.WriteEndAttribute();
+ xw.WriteStartAttribute("spans");
+ xw.WriteValue("1:" + row.Cells.Count().ToString());
+ xw.WriteEndAttribute();
+ int cellCount = 0;
+ foreach (var cell in row.Cells)
+ {
+ if (cell != null)
+ {
+ xw.WriteStartElement("c", ns);
+ xw.WriteStartAttribute("r");
+ xw.WriteValue(SpreadsheetMLUtil.IntToColumnId(cellCount) + rowCount.ToString());
+ xw.WriteEndAttribute();
+ if (cell.Bold != null ||
+ cell.Italic != null ||
+ cell.FormatCode != null ||
+ cell.HorizontalCellAlignment != null)
+ {
+ xw.WriteStartAttribute("s");
+ xw.WriteValue(GetCellStyle(sDoc, cell));
+ xw.WriteEndAttribute();
+ }
+ switch (cell.CellDataType)
+ {
+ case CellDataType.Boolean:
+ xw.WriteStartAttribute("t");
+ xw.WriteValue("b");
+ xw.WriteEndAttribute();
+ break;
+ case CellDataType.Date:
+ xw.WriteStartAttribute("t");
+ xw.WriteValue("d");
+ xw.WriteEndAttribute();
+ break;
+ case CellDataType.Number:
+ xw.WriteStartAttribute("t");
+ xw.WriteValue("n");
+ xw.WriteEndAttribute();
+ break;
+ case CellDataType.String:
+ xw.WriteStartAttribute("t");
+ xw.WriteValue("str");
+ xw.WriteEndAttribute();
+ break;
+ default:
+ xw.WriteStartAttribute("t");
+ xw.WriteValue("str");
+ xw.WriteEndAttribute();
+ break;
+ }
+ if (cell.Value != null)
+ {
+ xw.WriteStartElement("v", ns);
+ xw.WriteValue(cell.Value);
+ xw.WriteEndElement();
+ }
+ xw.WriteEndElement();
+ }
+ cellCount++;
+ }
+ xw.WriteEndElement();
+ numColumns = cellCount;
+ }
+
+ private static int GetCellStyle(SpreadsheetDocument sDoc, CellDfn cell)
+ {
+ XDocument sXDoc = sDoc.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ var match = sXDoc
+ .Root
+ .Element(S.cellXfs)
+ .Elements(S.xf)
+ .Select((e, i) => new
+ {
+ Element = e,
+ Index = i,
+ })
+ .FirstOrDefault(xf => CompareStyles(sXDoc, xf.Element, cell));
+ if (match != null)
+ return match.Index;
+
+ // if no match, then create a style
+ int newId = CreateNewStyle(sXDoc, cell, sDoc);
+ return newId;
+ }
+
+ private static int CreateNewStyle(XDocument sXDoc, CellDfn cell, SpreadsheetDocument sDoc)
+ {
+ XAttribute applyFont = null;
+ XAttribute fontId = null;
+ if (cell.Bold == true || cell.Italic == true)
+ {
+ applyFont = new XAttribute(SSNoNamespace.applyFont, 1);
+ fontId = new XAttribute(SSNoNamespace.fontId, GetFontId(sXDoc, cell));
+ }
+ XAttribute applyAlignment = null;
+ XElement alignment = null;
+ if (cell.HorizontalCellAlignment != null)
+ {
+ applyAlignment = new XAttribute(SSNoNamespace.applyAlignment, 1);
+ alignment = new XElement(S.alignment,
+ new XAttribute(SSNoNamespace.horizontal, cell.HorizontalCellAlignment.ToString().ToLower()));
+ }
+ XAttribute applyNumberFormat = null;
+ XAttribute numFmtId = null;
+ if (cell.FormatCode != null)
+ {
+ if (CellDfn.StandardFormats.ContainsKey(cell.FormatCode))
+ {
+ applyNumberFormat = new XAttribute(SSNoNamespace.applyNumberFormat, 1);
+ numFmtId = new XAttribute(SSNoNamespace.numFmtId, CellDfn.StandardFormats[cell.FormatCode]);
+ }
+ else
+ {
+ applyNumberFormat = new XAttribute(SSNoNamespace.applyNumberFormat, 1);
+ numFmtId = new XAttribute(SSNoNamespace.numFmtId, GetNumFmtId(sXDoc, cell.FormatCode));
+ }
+ }
+ XElement newXf = new XElement(S.xf,
+ applyFont,
+ fontId,
+ applyAlignment,
+ alignment,
+ applyNumberFormat,
+ numFmtId);
+ XElement cellXfs = sXDoc
+ .Root
+ .Element(S.cellXfs);
+ if (cellXfs == null)
+ {
+ cellXfs = new XElement(S.cellXfs,
+ new XAttribute(SSNoNamespace.count, 1),
+ newXf);
+ return 0;
+ }
+ else
+ {
+ int currentCount = (int)cellXfs.Attribute(SSNoNamespace.count);
+ cellXfs.SetAttributeValue(SSNoNamespace.count, currentCount + 1);
+ cellXfs.Add(newXf);
+ return currentCount;
+ }
+ }
+
+ private static int GetFontId(XDocument sXDoc, CellDfn cell)
+ {
+ XElement fonts = sXDoc.Root.Element(S.fonts);
+ if (fonts == null)
+ {
+ fonts = new XElement(S.fonts,
+ new XAttribute(SSNoNamespace.count, 1),
+ new XElement(S.font,
+ cell.Bold == true ? new XElement(S.b) : null,
+ cell.Italic == true ? new XElement(S.i) : null));
+ sXDoc.Root.Add(fonts);
+ return 0;
+ }
+ XElement font = new XElement(S.font,
+ cell.Bold == true ? new XElement(S.b) : null,
+ cell.Italic == true ? new XElement(S.i) : null);
+ fonts.Add(font);
+ int count = (int)fonts.Attribute(SSNoNamespace.count);
+ fonts.SetAttributeValue(SSNoNamespace.count, count + 1);
+ return count;
+ }
+
+ private static int GetNumFmtId(XDocument sXDoc, string formatCode)
+ {
+ int xfNumber = 81;
+ while (true)
+ {
+ if (!sXDoc
+ .Root
+ .Elements(S.numFmts)
+ .Elements(S.numFmt)
+ .Any(nf => (int)nf.Attribute(SSNoNamespace.numFmtId) == xfNumber))
+ break;
+ ++xfNumber;
+ }
+ XElement numFmts = sXDoc.Root.Element(S.numFmts);
+ if (numFmts == null)
+ {
+ numFmts = new XElement(S.numFmts,
+ new XAttribute(SSNoNamespace.count, 1),
+ new XElement(S.numFmt,
+ new XAttribute(SSNoNamespace.numFmtId, xfNumber),
+ new XAttribute(SSNoNamespace.formatCode, formatCode)));
+ sXDoc.Root.AddFirst(numFmts);
+ return xfNumber;
+ }
+ XElement numFmt = new XElement(S.numFmt,
+ new XAttribute(SSNoNamespace.numFmtId, xfNumber),
+ new XAttribute(SSNoNamespace.formatCode, formatCode));
+ numFmts.Add(numFmt);
+ return xfNumber;
+ }
+
+ private static bool CompareStyles(XDocument sXDoc, XElement xf, CellDfn cell)
+ {
+ bool matchFont = MatchFont(sXDoc, xf, cell);
+ bool matchAlignment = MatchAlignment(sXDoc, xf, cell);
+ bool matchFormat = MatchFormat(sXDoc, xf, cell);
+ return (matchFont && matchAlignment && matchFormat);
+ }
+
+ private static bool MatchFont(XDocument sXDoc, XElement xf, CellDfn cell)
+ {
+ if (((int?)xf.Attribute(SSNoNamespace.applyFont) == 0 ||
+ xf.Attribute(SSNoNamespace.applyFont) == null) &&
+ (cell.Bold == null || cell.Bold == false) &&
+ (cell.Italic == null || cell.Italic == false))
+ return true;
+ if (((int?)xf.Attribute(SSNoNamespace.applyFont) == 0 ||
+ xf.Attribute(SSNoNamespace.applyFont) == null) &&
+ (cell.Bold == true ||
+ cell.Italic == true))
+ return false;
+ int fontId = (int)xf.Attribute(SSNoNamespace.fontId);
+ XElement font = sXDoc
+ .Root
+ .Element(S.fonts)
+ .Elements(S.font)
+ .ElementAt(fontId);
+ XElement fabFont = new XElement(S.font,
+ cell.Bold == true ? new XElement(S.b) : null,
+ cell.Italic == true ? new XElement(S.i) : null);
+ bool match = XNode.DeepEquals(font, fabFont);
+ return match;
+ }
+
+ private static bool MatchAlignment(XDocument sXDoc, XElement xf, CellDfn cell)
+ {
+ if ((int?)xf.Attribute(SSNoNamespace.applyAlignment) == 0 ||
+ (xf.Attribute(SSNoNamespace.applyAlignment) == null) &&
+ cell.HorizontalCellAlignment == null)
+ return true;
+ if (xf.Attribute(SSNoNamespace.applyAlignment) == null &&
+ cell.HorizontalCellAlignment != null)
+ return false;
+ string alignment = (string)xf.Element(S.alignment).Attribute(SSNoNamespace.horizontal);
+ bool match = alignment == cell.HorizontalCellAlignment.ToString().ToLower();
+ return match;
+ }
+
+ private static bool MatchFormat(XDocument sXDoc, XElement xf, CellDfn cell)
+ {
+ if ((int?)xf.Attribute(SSNoNamespace.applyNumberFormat) != 1 &&
+ cell.FormatCode == null)
+ return true;
+ if (xf.Attribute(SSNoNamespace.applyNumberFormat) == null &&
+ cell.FormatCode != null)
+ return false;
+ int numFmtId = (int)xf.Attribute(SSNoNamespace.numFmtId);
+ int? nfi = null;
+ if (cell.FormatCode != null)
+ {
+ if (CellDfn.StandardFormats.ContainsKey(cell.FormatCode))
+ nfi = CellDfn.StandardFormats[cell.FormatCode];
+ if (nfi == numFmtId)
+ return true;
+ }
+ XElement numFmts = sXDoc
+ .Root
+ .Element(S.numFmts);
+ if (numFmts == null)
+ return false;
+ XElement numFmt = numFmts
+ .Elements(S.numFmt)
+ .FirstOrDefault(numFmtElement =>
+ (int)numFmtElement.Attribute(SSNoNamespace.numFmtId) == numFmtId);
+ if (numFmt == null)
+ return false;
+ string styleFormatCode = (string)numFmt.Attribute(SSNoNamespace.formatCode);
+ bool match = styleFormatCode == cell.FormatCode;
+ return match;
+ }
+
+ private static string _EmptyXlsx = @"UEsDBBQABgAIAAAAIQBi7p1oYQEAAJAEAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs
+lE1PwzAMhu9I/IcqV9Rm44AQWrcDH0eYxPgBoXHXaGkSxd7Y/j1u9iGEyqaJXRq1sd/3iWtnNFm3
+NltBRONdKYbFQGTgKq+Nm5fiY/aS34sMSTmtrHdQig2gmIyvr0azTQDMONthKRqi8CAlVg20Cgsf
+wPFO7WOriF/jXAZVLdQc5O1gcCcr7wgc5dRpiPHoCWq1tJQ9r/nzliSCRZE9bgM7r1KoEKypFDGp
+XDn9yyXfORScmWKwMQFvGEPIXodu52+DXd4blyYaDdlURXpVLWPItZVfPi4+vV8Ux0V6KH1dmwq0
+r5YtV6DAEEFpbACotUVai1YZt+c+4p+CUaZleGGQ7nxJ+AQH8f8GmZ7/R0gyJwyRNhbwwqfdip5y
+blQE/U6RJ+PiAD+1j3Fw30yjD8gTFOH8KuxHpMvOAwtBJAOHIelrtoMjT9/5hr+6Hbr51qB7vGW6
+T8bfAAAA//8DAFBLAwQUAAYACAAAACEAtVUwI/UAAABMAgAACwAIAl9yZWxzLy5yZWxzIKIEAiig
+AAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AIySz07DMAzG70i8Q+T76m5ICKGlu0xIuyFUHsAk7h+1jaMkQPf2hAOCSmPb0fbnzz9b3u7maVQf
+HGIvTsO6KEGxM2J712p4rZ9WD6BiImdpFMcajhxhV93ebF94pJSbYtf7qLKLixq6lPwjYjQdTxQL
+8exypZEwUcphaNGTGahl3JTlPYa/HlAtPNXBaggHeweqPvo8+bK3NE1veC/mfWKXToxAnhM7y3bl
+Q2YLqc/bqJpCy0mDFfOc0xHJ+yJjA54m2lxP9P+2OHEiS4nQSODzPN+Kc0Dr64Eun2ip+L3OPOKn
+hOFNZPhhwcUPVF8AAAD//wMAUEsDBBQABgAIAAAAIQCBPpSX9AAAALoCAAAaAAgBeGwvX3JlbHMv
+d29ya2Jvb2sueG1sLnJlbHMgogQBKKAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+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";
+
+ }
+
+ public class SpreadsheetWriterInternalException : Exception
+ {
+ public SpreadsheetWriterInternalException()
+ : base("Internal error - unexpected content in _EmptyXlsx.")
+ {
+ }
+ }
+
+ public class InvalidSheetNameException : Exception
+ {
+ public InvalidSheetNameException(string name)
+ : base(string.Format("The supplied name ({0}) is not a valid XLSX worksheet name.", name))
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenXmlPowerTools/StronglyTypedBlock.cs b/OpenXmlPowerTools/StronglyTypedBlock.cs
new file mode 100644
index 0000000..b591a2b
--- /dev/null
+++ b/OpenXmlPowerTools/StronglyTypedBlock.cs
@@ -0,0 +1,70 @@
+//
+// Copyright 2017 Thomas Barnekow
+//
+// 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
+//
+// Developer: Thomas Barnekow
+// Email: thomas@barnekow.info
+//
+
+using System;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ /// <summary>
+ /// Provides an elegant way of wrapping a set of invocations of the strongly typed
+ /// classes provided by the Open XML SDK) in a using statement that demarcates those
+ /// invokations as one "block" before and after which the PowerTools can be used safely.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This class lends itself to scenarios where the PowerTools and Linq-to-XML are used as
+ /// the primary API for working with Open XML elements, next to the strongly typed classes
+ /// provided by the Open XML SDK. In these scenarios, the class would be used as follows:
+ /// </para>
+ /// <code>
+ /// [Your code using the PowerTools]
+ ///
+ /// using (new NonPowerToolsBlock(wordprocessingDocument))
+ /// {
+ /// [Your code using the strongly typed classes]
+ /// }
+ ///
+ /// [Your code using the PowerTools]
+ /// </code>
+ /// <para>
+ /// Upon creation, instances of this class will invoke the
+ /// <see cref="PowerToolsBlockExtensions.EndPowerToolsBlock"/> method on the package
+ /// to begin the block. Upon disposal, instances of this class will call the
+ /// <see cref="PowerToolsBlockExtensions.BeginPowerToolsBlock"/> method on the package
+ /// to end the block.
+ /// </para>
+ /// </remarks>
+ /// <seealso cref="PowerToolsBlock"/>
+ /// <seealso cref="PowerToolsBlockExtensions.BeginPowerToolsBlock"/>
+ /// <seealso cref="PowerToolsBlockExtensions.EndPowerToolsBlock"/>
+ public class StronglyTypedBlock : IDisposable
+ {
+ private OpenXmlPackage _package;
+
+ public StronglyTypedBlock(OpenXmlPackage package)
+ {
+ if (package == null) throw new ArgumentNullException("package");
+
+ _package = package;
+ _package.EndPowerToolsBlock();
+ }
+
+ public void Dispose()
+ {
+ if (_package == null) return;
+
+ _package.BeginPowerToolsBlock();
+ _package = null;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/TestUtil.cs b/OpenXmlPowerTools/TestUtil.cs
new file mode 100644
index 0000000..06e4fcc
--- /dev/null
+++ b/OpenXmlPowerTools/TestUtil.cs
@@ -0,0 +1,88 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OpenXmlPowerTools
+{
+ public class TestUtil
+ {
+ public static DirectoryInfo SourceDir = new DirectoryInfo("../../../../TestFiles/");
+ private static bool? s_DeleteTempFiles = null;
+
+ public static bool DeleteTempFiles
+ {
+ get
+ {
+ if (s_DeleteTempFiles != null)
+ return (bool)s_DeleteTempFiles;
+ FileInfo donotdelete = new FileInfo("donotdelete.txt");
+ s_DeleteTempFiles = !donotdelete.Exists;
+ return (bool)s_DeleteTempFiles;
+ }
+ }
+
+ private static DirectoryInfo s_TempDir = null;
+ public static DirectoryInfo TempDir
+ {
+ get
+ {
+ if (s_TempDir != null)
+ return s_TempDir;
+ else
+ {
+ var now = DateTime.Now;
+ var tempDirName = String.Format("Test-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", now.Year - 2000, now.Month, now.Day, now.Hour, now.Minute, now.Second);
+ s_TempDir = new DirectoryInfo(Path.Combine(".", tempDirName));
+ s_TempDir.Create();
+ return s_TempDir;
+ }
+ }
+ }
+
+ public static void NotePad(string str)
+ {
+ var guidName = Guid.NewGuid().ToString().Replace("-", "") + ".txt";
+ var fi = new FileInfo(Path.Combine(TempDir.FullName, guidName));
+ File.WriteAllText(fi.FullName, str);
+ var notepadExe = new FileInfo(@"C:\Program Files (x86)\Notepad++\notepad++.exe");
+ if (!notepadExe.Exists)
+ notepadExe = new FileInfo(@"C:\Program Files\Notepad++\notepad++.exe");
+ if (!notepadExe.Exists)
+ notepadExe = new FileInfo(@"C:\Windows\System32\notepad.exe");
+ ExecutableRunner.RunExecutable(notepadExe.FullName, fi.FullName, TempDir.FullName);
+ }
+
+ public static void KDiff3(FileInfo oldFi, FileInfo newFi)
+ {
+ var kdiffExe = new FileInfo(@"C:\Program Files (x86)\KDiff3\kdiff3.exe");
+ var result = ExecutableRunner.RunExecutable(kdiffExe.FullName, oldFi.FullName + " " + newFi.FullName, TempDir.FullName);
+ }
+
+ public static void Explorer(DirectoryInfo di)
+ {
+ Process.Start(di.FullName);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/TextReplacer.cs b/OpenXmlPowerTools/TextReplacer.cs
new file mode 100644
index 0000000..a016d3b
--- /dev/null
+++ b/OpenXmlPowerTools/TextReplacer.cs
@@ -0,0 +1,443 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public partial class WmlDocument : OpenXmlPowerToolsDocument
+ {
+ public WmlDocument SearchAndReplace(string search, string replace, bool matchCase)
+ {
+ return TextReplacer.SearchAndReplace(this, search, replace, matchCase);
+ }
+ }
+
+ public partial class PmlDocument : OpenXmlPowerToolsDocument
+ {
+ public PmlDocument SearchAndReplace(string search, string replace, bool matchCase)
+ {
+ return TextReplacer.SearchAndReplace(this, search, replace, matchCase);
+ }
+ }
+
+ public class TextReplacer
+ {
+ private class MatchSemaphore
+ {
+ public int MatchId;
+ public MatchSemaphore(int matchId)
+ {
+ MatchId = matchId;
+ }
+ }
+
+ private static XObject CloneWithAnnotation(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ XElement newElement = new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => CloneWithAnnotation(n)));
+ if (element.Annotation<MatchSemaphore>() != null)
+ newElement.AddAnnotation(element.Annotation<MatchSemaphore>());
+ }
+ return node;
+ }
+
+ private static object WmlSearchAndReplaceTransform(XNode node,
+ string search, string replace, bool matchCase)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ {
+ string contents = element.Descendants(W.t).Select(t => (string)t).StringConcatenate();
+ if (contents.Contains(search) ||
+ (!matchCase && contents.ToUpper().Contains(search.ToUpper())))
+ {
+ XElement paragraphWithSplitRuns = new XElement(W.p,
+ element.Attributes(),
+ element.Nodes().Select(n => WmlSearchAndReplaceTransform(n, search,
+ replace, matchCase)));
+ XElement[] subRunArray = paragraphWithSplitRuns
+ .Elements(W.r)
+ .Where(e => {
+ XElement subRunElement = e.Elements().FirstOrDefault(el => el.Name != W.rPr);
+ if (subRunElement == null)
+ return false;
+ return W.SubRunLevelContent.Contains(subRunElement.Name);
+ })
+ .ToArray();
+ int paragraphChildrenCount = subRunArray.Length;
+ int matchId = 1;
+ foreach (var pc in subRunArray
+ .Take(paragraphChildrenCount - (search.Length - 1))
+ .Select((c, i) => new { Child = c, Index = i, }))
+ {
+ var subSequence = subRunArray.SequenceAt(pc.Index).Take(search.Length);
+ var zipped = subSequence.PtZip(search, (pcp, c) => new
+ {
+ ParagraphChildProjection = pcp,
+ CharacterToCompare = c,
+ });
+ bool dontMatch = zipped.Any(z => {
+ if (z.ParagraphChildProjection.Annotation<MatchSemaphore>() != null)
+ return true;
+ bool b;
+ if (matchCase)
+ b = z.ParagraphChildProjection.Value != z.CharacterToCompare.ToString();
+ else
+ b = z.ParagraphChildProjection.Value.ToUpper() != z.CharacterToCompare.ToString().ToUpper();
+ return b;
+ });
+ bool match = !dontMatch;
+ if (match)
+ {
+ foreach (var item in subSequence)
+ item.AddAnnotation(new MatchSemaphore(matchId));
+ ++matchId;
+ }
+ }
+
+ // The following code is locally impure, as this is the most expressive way to write it.
+ XElement paragraphWithReplacedRuns = (XElement)CloneWithAnnotation(paragraphWithSplitRuns);
+ for (int id = 1; id < matchId; ++id)
+ {
+ List<XElement> elementsToReplace = paragraphWithReplacedRuns
+ .Elements()
+ .Where(e => {
+ var sem = e.Annotation<MatchSemaphore>();
+ if (sem == null)
+ return false;
+ return sem.MatchId == id;
+ })
+ .ToList();
+ elementsToReplace.First().AddBeforeSelf(
+ new XElement(W.r,
+ elementsToReplace.First().Elements(W.rPr),
+ new XElement(W.t, replace)));
+ elementsToReplace.Remove();
+ }
+ var groupedAdjacentRunsWithIdenticalFormatting =
+ paragraphWithReplacedRuns
+ .Elements()
+ .GroupAdjacent(ce =>
+ {
+ if (ce.Name != W.r)
+ return "DontConsolidate";
+ if (ce.Elements().Where(e => e.Name != W.rPr).Count() != 1 ||
+ ce.Element(W.t) == null)
+ return "DontConsolidate";
+ if (ce.Element(W.rPr) == null)
+ return "";
+ return ce.Element(W.rPr).ToString(SaveOptions.None);
+ });
+ XElement paragraphWithConsolidatedRuns = new XElement(W.p,
+ groupedAdjacentRunsWithIdenticalFormatting.Select(g =>
+ {
+ if (g.Key == "DontConsolidate")
+ return (object)g;
+ string textValue = g.Select(r => r.Element(W.t).Value).StringConcatenate();
+ XAttribute xs = null;
+ if (textValue[0] == ' ' || textValue[textValue.Length - 1] == ' ')
+ xs = new XAttribute(XNamespace.Xml + "space", "preserve");
+ return new XElement(W.r,
+ g.First().Elements(W.rPr),
+ new XElement(W.t, xs, textValue));
+ }));
+ return paragraphWithConsolidatedRuns;
+ }
+ return element;
+ }
+ if (element.Name == W.r && element.Elements(W.t).Any())
+ {
+ var collectionOfRuns = element.Elements()
+ .Where(e => e.Name != W.rPr)
+ .Select(e =>
+ {
+ if (e.Name == W.t)
+ {
+ string s = (string)e;
+ IEnumerable<XElement> collectionOfSubRuns = s.Select(c =>
+ {
+ XElement newRun = new XElement(W.r,
+ element.Elements(W.rPr),
+ new XElement(W.t,
+ c == ' ' ?
+ new XAttribute(XNamespace.Xml + "space", "preserve") :
+ null, c));
+ return newRun;
+ });
+ return (object)collectionOfSubRuns;
+ }
+ else
+ {
+ XElement newRun = new XElement(W.r,
+ element.Elements(W.rPr),
+ e);
+ return newRun;
+ }
+ });
+ return collectionOfRuns;
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => WmlSearchAndReplaceTransform(n,
+ search, replace, matchCase)));
+ }
+ return node;
+ }
+
+ private static void WmlSearchAndReplaceInXDocument(XDocument xDocument, string search,
+ string replace, bool matchCase)
+ {
+ XElement newRoot = (XElement)WmlSearchAndReplaceTransform(xDocument.Root,
+ search, replace, matchCase);
+ xDocument.Elements().First().ReplaceWith(newRoot);
+ }
+
+ public static WmlDocument SearchAndReplace(WmlDocument doc, string search, string replace, bool matchCase)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ {
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ SearchAndReplace(document, search, replace, matchCase);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void SearchAndReplace(WordprocessingDocument wordDoc, string search,
+ string replace, bool matchCase)
+ {
+ if (RevisionAccepter.HasTrackedRevisions(wordDoc))
+ throw new InvalidDataException(
+ "Search and replace will not work with documents " +
+ "that contain revision tracking.");
+ XDocument xDoc;
+ xDoc = wordDoc.MainDocumentPart.DocumentSettingsPart.GetXDocument();
+ if (xDoc.Descendants(W.trackRevisions).Any())
+ throw new InvalidDataException("Revision tracking is turned on for document.");
+
+ xDoc = wordDoc.MainDocumentPart.GetXDocument();
+ WmlSearchAndReplaceInXDocument(xDoc, search, replace, matchCase);
+ wordDoc.MainDocumentPart.PutXDocument();
+ foreach (var part in wordDoc.MainDocumentPart.HeaderParts)
+ {
+ xDoc = part.GetXDocument();
+ WmlSearchAndReplaceInXDocument(xDoc, search, replace, matchCase);
+ part.PutXDocument();
+ }
+ foreach (var part in wordDoc.MainDocumentPart.FooterParts)
+ {
+ xDoc = part.GetXDocument();
+ WmlSearchAndReplaceInXDocument(xDoc, search, replace, matchCase);
+ part.PutXDocument();
+ }
+ if (wordDoc.MainDocumentPart.EndnotesPart != null)
+ {
+ xDoc = wordDoc.MainDocumentPart.EndnotesPart.GetXDocument();
+ WmlSearchAndReplaceInXDocument(xDoc, search, replace, matchCase);
+ wordDoc.MainDocumentPart.EndnotesPart.PutXDocument();
+ }
+ if (wordDoc.MainDocumentPart.FootnotesPart != null)
+ {
+ xDoc = wordDoc.MainDocumentPart.FootnotesPart.GetXDocument();
+ WmlSearchAndReplaceInXDocument(xDoc, search, replace, matchCase);
+ wordDoc.MainDocumentPart.FootnotesPart.PutXDocument();
+ }
+ }
+
+ private static object PmlReplaceTextTransform(XNode node, string search, string replace,
+ bool matchCase)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == A.p)
+ {
+ string contents = element.Descendants(A.t).Select(t => (string)t).StringConcatenate();
+ if (contents.Contains(search) ||
+ (!matchCase && contents.ToUpper().Contains(search.ToUpper())))
+ {
+ XElement paragraphWithSplitRuns = new XElement(A.p,
+ element.Attributes(),
+ element.Nodes().Select(n => PmlReplaceTextTransform(n, search,
+ replace, matchCase)));
+ XElement[] subRunArray = paragraphWithSplitRuns
+ .Elements(A.r)
+ .Where(e =>
+ {
+ XElement subRunElement = e.Elements().FirstOrDefault(el => el.Name != A.rPr);
+ if (subRunElement == null)
+ return false;
+ return subRunElement.Name == A.t;
+ })
+ .ToArray();
+ int paragraphChildrenCount = subRunArray.Length;
+ int matchId = 1;
+ foreach (var pc in subRunArray
+ .Take(paragraphChildrenCount - (search.Length - 1))
+ .Select((c, i) => new { Child = c, Index = i, }))
+ {
+ var subSequence = subRunArray.SequenceAt(pc.Index).Take(search.Length);
+ var zipped = subSequence.PtZip(search, (pcp, c) => new
+ {
+ ParagraphChildProjection = pcp,
+ CharacterToCompare = c,
+ });
+ bool dontMatch = zipped.Any(z =>
+ {
+ if (z.ParagraphChildProjection.Annotation<MatchSemaphore>() != null)
+ return true;
+ bool b;
+ if (matchCase)
+ b = z.ParagraphChildProjection.Value != z.CharacterToCompare.ToString();
+ else
+ b = z.ParagraphChildProjection.Value.ToUpper() != z.CharacterToCompare.ToString().ToUpper();
+ return b;
+ });
+ bool match = !dontMatch;
+ if (match)
+ {
+ foreach (var item in subSequence)
+ item.AddAnnotation(new MatchSemaphore(matchId));
+ ++matchId;
+ }
+ }
+
+ // The following code is locally impure, as this is the most expressive way to write it.
+ XElement paragraphWithReplacedRuns = (XElement)CloneWithAnnotation(paragraphWithSplitRuns);
+ for (int id = 1; id < matchId; ++id)
+ {
+ List<XElement> elementsToReplace = paragraphWithReplacedRuns
+ .Elements()
+ .Where(e =>
+ {
+ var sem = e.Annotation<MatchSemaphore>();
+ if (sem == null)
+ return false;
+ return sem.MatchId == id;
+ })
+ .ToList();
+ elementsToReplace.First().AddBeforeSelf(
+ new XElement(A.r,
+ elementsToReplace.First().Elements(A.rPr),
+ new XElement(A.t, replace)));
+ elementsToReplace.Remove();
+ }
+
+ var groupedAdjacentRunsWithIdenticalFormatting =
+ paragraphWithReplacedRuns
+ .Elements()
+ .GroupAdjacent(ce =>
+ {
+ if (ce.Name != A.r)
+ return "DontConsolidate";
+ if (ce.Elements().Where(e => e.Name != A.rPr).Count() != 1 ||
+ ce.Element(A.t) == null)
+ return "DontConsolidate";
+ if (ce.Element(A.rPr) == null)
+ return "";
+ return ce.Element(A.rPr).ToString(SaveOptions.None);
+ });
+ XElement paragraphWithConsolidatedRuns = new XElement(A.p,
+ groupedAdjacentRunsWithIdenticalFormatting.Select(g =>
+ {
+ if (g.Key == "DontConsolidate")
+ return (object)g;
+ string textValue = g.Select(r => r.Element(A.t).Value).StringConcatenate();
+ return new XElement(A.r,
+ g.First().Elements(A.rPr),
+ new XElement(A.t, textValue));
+ }));
+ return paragraphWithConsolidatedRuns;
+ }
+ }
+ if (element.Name == A.r && element.Elements(A.t).Any())
+ {
+ var collectionOfRuns = element.Elements()
+ .Where(e => e.Name != A.rPr)
+ .Select(e =>
+ {
+ if (e.Name == A.t)
+ {
+ string s = (string)e;
+ IEnumerable<XElement> collectionOfSubRuns = s.Select(c =>
+ {
+ XElement newRun = new XElement(A.r,
+ element.Elements(A.rPr),
+ new XElement(A.t, c));
+ return newRun;
+ });
+ return (object)collectionOfSubRuns;
+ }
+ else
+ {
+ XElement newRun = new XElement(A.r,
+ element.Elements(A.rPr),
+ e);
+ return newRun;
+ }
+ });
+ return collectionOfRuns;
+ }
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => PmlReplaceTextTransform(n, search, replace, matchCase)));
+ }
+ return node;
+ }
+
+ public static PmlDocument SearchAndReplace(PmlDocument doc, string search, string replace, bool matchCase)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ {
+ using (PresentationDocument document = streamDoc.GetPresentationDocument())
+ {
+ SearchAndReplace(document, search, replace, matchCase);
+ }
+ return streamDoc.GetModifiedPmlDocument();
+ }
+ }
+
+ public static void SearchAndReplace(PresentationDocument pDoc, string search,
+ string replace, bool matchCase)
+ {
+ PresentationPart presentationPart = pDoc.PresentationPart;
+ foreach (var slidePart in presentationPart.SlideParts)
+ {
+ XDocument slideXDoc = slidePart.GetXDocument();
+ XElement root = slideXDoc.Root;
+ XElement newRoot = (XElement)PmlReplaceTextTransform(root, search, replace, matchCase);
+ slidePart.PutXDocument(new XDocument(newRoot));
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/UnicodeMapper.cs b/OpenXmlPowerTools/UnicodeMapper.cs
new file mode 100644
index 0000000..10654f5
--- /dev/null
+++ b/OpenXmlPowerTools/UnicodeMapper.cs
@@ -0,0 +1,334 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2016.
+
+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
+
+Developer: Thomas Barnekow
+Email: thomas@barnekow.info
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+
+namespace OpenXmlPowerTools
+{
+ public class UnicodeMapper
+ {
+ // Unicode character values.
+ public static readonly char StartOfHeading = '\u0001';
+ public static readonly char HorizontalTabulation = '\u0009';
+ public static readonly char LineFeed = '\u000A';
+ public static readonly char FormFeed = '\u000C';
+ public static readonly char CarriageReturn = '\u000D';
+ public static readonly char SoftHyphen = '\u00AD';
+ public static readonly char NonBreakingHyphen = '\u2011';
+
+ // Unicode area boundaries.
+ public static readonly char StartOfPrivateUseArea = '\uE000';
+ public static readonly char StartOfSymbolArea = '\uF000';
+ public static readonly char EndOfPrivateUseArea = '\uF8FF';
+
+ // Dictionaries for w:sym stringification.
+ private static readonly Dictionary<string, char> SymStringToUnicodeCharDictionary =
+ new Dictionary<string, char>();
+
+ private static readonly Dictionary<char, XElement> UnicodeCharToSymDictionary =
+ new Dictionary<char, XElement>();
+
+ // Represents the Unicode value that was last used to map an actual character
+ // onto a special value in the private use area, which starts at U+E000.
+ // In Open XML, U+F000 is added to the actual Unicode values, so we should be
+ // well outside that range and would have to map 4096 different characters
+ // to get into the area starting at U+F000.
+ private static char _lastUnicodeChar = StartOfPrivateUseArea;
+
+ /// <summary>
+ /// Stringify an Open XML run, turning (a) w:t, w:br, w:cr, w:noBreakHyphen,
+ /// w:softHyphen, w:sym, and w:tab into their corresponding Unicode strings
+ /// and (b) everything else into U+0001.
+ /// </summary>
+ /// <param name="element">An Open XML run or run child element.</param>
+ /// <returns>The corresponding Unicode value or U+0001.</returns>
+ public static string RunToString(XElement element)
+ {
+ if (element.Name == W.r && (element.Parent == null || element.Parent.Name != W.del))
+ return element.Elements().Select(RunToString).StringConcatenate();
+
+ // We need to ignore run properties.
+ if (element.Name == W.rPr)
+ return string.Empty;
+
+ // For w:t elements, we obviously want the element's value.
+ if (element.Name == W.t)
+ return (string) element;
+
+ // Turn elements representing special characters into their corresponding
+ // unicode characters.
+ if (element.Name == W.br)
+ {
+ XAttribute typeAttribute = element.Attribute(W.type);
+ string type = typeAttribute != null ? typeAttribute.Value : null;
+ if (type == null || type == "textWrapping")
+ return CarriageReturn.ToString();
+ if (type == "page")
+ return FormFeed.ToString();
+ }
+
+ if (element.Name == W.cr)
+ return CarriageReturn.ToString();
+ if (element.Name == W.noBreakHyphen)
+ return NonBreakingHyphen.ToString();
+ if (element.Name == W.softHyphen)
+ return SoftHyphen.ToString();
+ if (element.Name == W.tab)
+ return HorizontalTabulation.ToString();
+
+ if (element.Name == W.fldChar)
+ {
+ var fldCharType = element.Attributes(W.fldCharType).Select(a => a.Value).FirstOrDefault();
+ switch (fldCharType)
+ {
+ case "begin":
+ return "{";
+ case "end":
+ return "}";
+ default:
+ return "_";
+ }
+ }
+
+ if (element.Name == W.instrText)
+ return "_";
+
+ // Turn w:sym elements into Unicode character values. A w:char attribute
+ // value can be stored (a) directly in its Unicode character value from
+ // the font glyph or (b) in a Unicode character value created by adding
+ // U+F000 to the character value, thereby shifting the value into the
+ // Unicode private use area.
+ if (element.Name == W.sym)
+ return SymToChar(element).ToString();
+
+ // Elements we don't recognize will be turned into a character that
+ // doesn't typically appear in documents.
+ return StartOfHeading.ToString();
+ }
+
+ /// <summary>
+ /// Translate a symbol into a Unicode character, using the specified w:font attribute
+ /// value and unicode value (represented by the w:sym element's w:char attribute),
+ /// using a substitute value for the actual Unicode value if the same Unicode value
+ /// is already used in conjunction with a different w:font attribute value.
+ ///
+ /// Add U+F000 to the Unicode value if the specified value is less than U+1000, which
+ /// shifts the value into the Unicode private use area (which is also done by MS Word).
+ /// </summary>
+ /// <remarks>
+ /// For w:sym elements, the w:char attribute value is typically greater than "F000",
+ /// because U+F000 is added to the actual Unicode value to shift the value into
+ /// the Unicode private use area.
+ /// </remarks>
+ /// <param name="fontAttributeValue">The w:font attribute value, e.g., "Wingdings".</param>
+ /// <param name="unicodeValue">The unicode value.</param>
+ /// <returns>The Unicode character used to represent the symbol.</returns>
+ public static char SymToChar(string fontAttributeValue, char unicodeValue)
+ {
+ return SymToChar(fontAttributeValue, (int) unicodeValue);
+ }
+
+ /// <summary>
+ /// Translate a symbol into a Unicode character, using the specified w:font attribute
+ /// value and unicode value (represented by the w:sym element's w:char attribute),
+ /// using a substitute value for the actual Unicode value if the same Unicode value
+ /// is already used in conjunction with a different w:font attribute value.
+ ///
+ /// Add U+F000 to the Unicode value if the specified value is less than U+1000, which
+ /// shifts the value into the Unicode private use area (which is also done by MS Word).
+ /// </summary>
+ /// <remarks>
+ /// For w:sym elements, the w:char attribute value is typically greater than "F000",
+ /// because U+F000 is added to the actual Unicode value to shift the value into
+ /// the Unicode private use area.
+ /// </remarks>
+ /// <param name="fontAttributeValue">The w:font attribute value, e.g., "Wingdings".</param>
+ /// <param name="unicodeValue">The unicode value.</param>
+ /// <returns>The Unicode character used to represent the symbol.</returns>
+ public static char SymToChar(string fontAttributeValue, int unicodeValue)
+ {
+ int effectiveUnicodeValue = unicodeValue < 0x1000 ? 0xF000 + unicodeValue : unicodeValue;
+ return SymToChar(fontAttributeValue, effectiveUnicodeValue.ToString("X4"));
+ }
+
+ /// <summary>
+ /// Translate a symbol into a Unicode character, using the specified w:font and
+ /// w:char attribute values, using a substitute value for the actual Unicode
+ /// value if the same Unicode value is already used in conjunction with a different
+ /// w:font attribute value.
+ ///
+ /// Do not alter the w:char attribute value.
+ /// </summary>
+ /// <remarks>
+ /// For w:sym elements, the w:char attribute value is typically greater than "F000",
+ /// because U+F000 is added to the actual Unicode value to shift the value into
+ /// the Unicode private use area.
+ /// </remarks>
+ /// <param name="fontAttributeValue">The w:font attribute value, e.g., "Wingdings".</param>
+ /// <param name="charAttributeValue">The w:char attribute value, e.g., "F028".</param>
+ /// <returns>The Unicode character used to represent the symbol.</returns>
+ public static char SymToChar(string fontAttributeValue, string charAttributeValue)
+ {
+ if (string.IsNullOrEmpty(fontAttributeValue))
+ throw new ArgumentException("Argument is null or empty.", "fontAttributeValue");
+ if (string.IsNullOrEmpty(charAttributeValue))
+ throw new ArgumentException("Argument is null or empty.", "charAttributeValue");
+
+ return SymToChar(new XElement(W.sym,
+ new XAttribute(W.font, fontAttributeValue),
+ new XAttribute(W._char, charAttributeValue),
+ new XAttribute(XNamespace.Xmlns + "w", W.w)));
+ }
+
+ /// <summary>
+ /// Represent a w:sym element as a Unicode value, mapping the Unicode value
+ /// specified in the w:char attribute to a substitute value to be able to
+ /// use a Unicode value in conjunction with different fonts.
+ /// </summary>
+ /// <param name="sym">The w:sym element to be stringified.</param>
+ /// <returns>A single-character Unicode string representing the w:sym element.</returns>
+ public static char SymToChar(XElement sym)
+ {
+ if (sym == null)
+ throw new ArgumentNullException("sym");
+ if (sym.Name != W.sym)
+ throw new ArgumentException(string.Format("Not a w:sym: {0}", sym.Name), "sym");
+
+ XAttribute fontAttribute = sym.Attribute(W.font);
+ string fontAttributeValue = fontAttribute != null ? fontAttribute.Value : null;
+ if (fontAttributeValue == null)
+ throw new ArgumentException("w:sym element has no w:font attribute.", "sym");
+
+ XAttribute charAttribute = sym.Attribute(W._char);
+ string charAttributeValue = charAttribute != null ? charAttribute.Value : null;
+ if (charAttributeValue == null)
+ throw new ArgumentException("w:sym element has no w:char attribute.", "sym");
+
+ // Return Unicode value if it is in the dictionary.
+ var standardizedSym = new XElement(W.sym,
+ new XAttribute(W.font, fontAttributeValue),
+ new XAttribute(W._char, charAttributeValue),
+ new XAttribute(XNamespace.Xmlns + "w", W.w));
+ string standardizedSymString = standardizedSym.ToString(SaveOptions.None);
+ if (SymStringToUnicodeCharDictionary.ContainsKey(standardizedSymString))
+ return SymStringToUnicodeCharDictionary[standardizedSymString];
+
+ // Determine Unicode value to be used to represent the current w:sym element.
+ // Use the actual Unicode value if it has not yet been used with another font.
+ // Otherwise, create a special Unicode value in the private use area to represent
+ // the current w:sym element.
+ var unicodeChar = (char) Convert.ToInt32(charAttributeValue, 16);
+ if (UnicodeCharToSymDictionary.ContainsKey(unicodeChar))
+ unicodeChar = ++_lastUnicodeChar;
+
+ SymStringToUnicodeCharDictionary.Add(standardizedSymString, unicodeChar);
+ UnicodeCharToSymDictionary.Add(unicodeChar, standardizedSym);
+ return unicodeChar;
+ }
+
+ /// <summary>
+ /// Turn the specified text value into a list of runs with coalesced text elements.
+ /// Each run will have the specified run properties.
+ /// </summary>
+ /// <param name="textValue">The text value to transform.</param>
+ /// <param name="runProperties">The run properties to apply.</param>
+ /// <returns>A list of runs representing the text value.</returns>
+ public static List<XElement> StringToCoalescedRunList(string textValue, XElement runProperties)
+ {
+ return textValue
+ .Select(CharToRunChild)
+ .GroupAdjacent(e => e.Name == W.t)
+ .SelectMany(grouping => grouping.Key
+ ? StringToSingleRunList(grouping.Select(t => (string) t).StringConcatenate(), runProperties)
+ : grouping.Select(e => new XElement(W.r, runProperties, e)))
+ .ToList();
+ }
+
+ /// <summary>
+ /// Turn the specified text value into a list consisting of a single run having one
+ /// text element with that text value. The run will have the specified run properties.
+ /// </summary>
+ /// <param name="textValue">The text value to transform.</param>
+ /// <param name="runProperties">The run properties to apply.</param>
+ /// <returns>A list with a single run.</returns>
+ public static IEnumerable<XElement> StringToSingleRunList(string textValue, XElement runProperties)
+ {
+ var run = new XElement(W.r,
+ runProperties,
+ new XElement(W.t, XmlUtil.GetXmlSpaceAttribute(textValue), textValue));
+ return new List<XElement> { run };
+ }
+
+ /// <summary>
+ /// Turn the specified text value into a list of runs, each having the specified
+ /// run properties.
+ /// </summary>
+ /// <param name="textValue">The text value to transform.</param>
+ /// <param name="runProperties">The run properties to apply.</param>
+ /// <returns>A list of runs representing the text value.</returns>
+ public static List<XElement> StringToRunList(string textValue, XElement runProperties)
+ {
+ return textValue.Select(character => CharToRun(character, runProperties)).ToList();
+ }
+
+ /// <summary>
+ /// Create a w:r element from the specified character, which will be turned
+ /// into a corresponding Open XML element (e.g., w:t, w:br, w:tab).
+ /// </summary>
+ /// <param name="character">The character.</param>
+ /// <param name="runProperties">The w:rPr element to be added to the w:r element.</param>
+ /// <returns>The w:r element.</returns>
+ public static XElement CharToRun(char character, XElement runProperties)
+ {
+ return new XElement(W.r, runProperties, CharToRunChild(character));
+ }
+
+ /// <summary>
+ /// Create an Open XML element (e.g., w:t, w:br, w:tab) from the specified
+ /// character.
+ /// </summary>
+ /// <param name="character">The character.</param>
+ /// <returns>The Open XML element or null, if the character equals <see cref="StartOfHeading" /> (U+0001).</returns>
+ public static XElement CharToRunChild(char character)
+ {
+ // Ignore the special character that represents the Open XML elements we
+ // wanted to ignore.
+ if (character == StartOfHeading)
+ return null;
+
+ // Translate special characters into their corresponding Open XML elements.
+ // Turn a Carriage Return into an empty w:br element, regardless of whether
+ // the former was created from an equivalent w:cr element.
+ if (character == CarriageReturn)
+ return new XElement(W.br);
+ if (character == FormFeed)
+ return new XElement(W.br, new XAttribute(W.type, "page"));
+ if (character == HorizontalTabulation)
+ return new XElement(W.tab);
+ if (character == NonBreakingHyphen)
+ return new XElement(W.noBreakHyphen);
+ if (character == SoftHyphen)
+ return new XElement(W.softHyphen);
+
+ // Translate symbol characters into their corresponding w:sym elements.
+ if (UnicodeCharToSymDictionary.ContainsKey(character))
+ return UnicodeCharToSymDictionary[character];
+
+ // Turn "normal" characters into text elements.
+ return new XElement(W.t, XmlUtil.GetXmlSpaceAttribute(character), character);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/WmlComparer.cs b/OpenXmlPowerTools/WmlComparer.cs
new file mode 100644
index 0000000..efdb19d
--- /dev/null
+++ b/OpenXmlPowerTools/WmlComparer.cs
@@ -0,0 +1,7415 @@
+/***************************************************************************
+
+Copyright (c) Eric White 2016.
+
+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
+
+Published at http://EricWhite.com
+Resource Center and Documentation: http://ericwhite.com/blog/blog/open-xml-powertools-developer-center/
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Globalization;
+using System.IO;
+using System.IO.Packaging;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.Drawing;
+using System.Security.Cryptography;
+using OpenXmlPowerTools;
+
+// It is possible to optimize DescendantContentAtoms
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/// Currently, the unid is set at the beginning of the algorithm. It is used by the code that establishes correlation based on first rejecting
+/// tracked revisions, then correlating paragraphs/tables. It is requred for this algorithm - after finding a correlated sequence in the document with rejected
+/// revisions, it uses the unid to find the same paragraph in the document without rejected revisions, then sets the correlated sha1 hash in that document.
+///
+/// But then when accepting tracked revisions, for certain paragraphs (where there are deleted paragraph marks) it is going to lose the unids. But this isn't a
+/// problem because when paragraph marks are deleted, the correlation is definitely no longer possible. Any paragraphs that are in a range of paragraphs that
+/// are coalesced can't be correlated to paragraphs in the other document via their hash. At that point we no longer care what their unids are.
+///
+/// But after that it is only used to reconstruct the tree. It is also used in the debugging code that
+/// prints the various correlated sequences and comparison units - this is display for debugging purposes only.
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/// The key idea here is that a given paragraph will always have the same ancestors, and it doesn't matter whether the content was deleted from the old document,
+/// inserted into the new document, or set as equal. At this point, we identify a paragraph as a sequential list of content atoms, terminated by a paragraph mark.
+/// This entire list will for a single paragraph, regardless of whether the paragraph is a child of the body, or if the paragraph is in a cell in a table, or if
+/// the paragraph is in a text box. The list of ancestors, from the paragraph to the root of the XML tree will be the same for all content atoms in the paragraph.
+///
+/// Therefore:
+///
+/// Iterate through the list of content atoms backwards. When the loop sees a paragraph mark, it gets the ancestor unids from the paragraph mark to the top of the
+/// tree, and sets this as the same for all content atoms in the paragraph. For descendants of the paragraph mark, it doesn't really matter if content is put into
+/// separate runs or what not. We don't need to be concerned about what the unids are for descendants of the paragraph.
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+namespace OpenXmlPowerTools
+{
+ public class WmlComparerSettings
+ {
+ public char[] WordSeparators;
+ public string AuthorForRevisions = "Open-Xml-PowerTools";
+ public string DateTimeForRevisions = DateTime.Now.ToString("o");
+ public double DetailThreshold = 0.15;
+ public bool CaseInsensitive = false;
+ public CultureInfo CultureInfo = null;
+ public Action<string> LogCallback = null;
+ public int StartingIdForFootnotesEndnotes = 1;
+
+ public DirectoryInfo DebugTempFileDi;
+
+ public WmlComparerSettings()
+ {
+ // note that , and . are processed explicitly to handle cases where they are in a number or word
+ WordSeparators = new[] { ' ', '-', ')', '(', ';', ',' }; // todo need to fix this for complete list
+ }
+ }
+
+ public class WmlComparerConsolidateSettings
+ {
+ public bool ConsolidateWithTable = true;
+ }
+
+ public class WmlRevisedDocumentInfo
+ {
+ public WmlDocument RevisedDocument;
+ public string Revisor;
+ public Color Color;
+ }
+
+ public static class WmlComparer
+ {
+ public static bool s_False = false;
+ public static bool s_True = true;
+ public static bool s_SaveIntermediateFilesForDebugging = false;
+
+ public static WmlDocument Compare(WmlDocument source1, WmlDocument source2, WmlComparerSettings settings)
+ {
+ return CompareInternal(source1, source2, settings, true);
+ }
+
+ private static WmlDocument CompareInternal(WmlDocument source1, WmlDocument source2, WmlComparerSettings settings,
+ bool preProcessMarkupInOriginal)
+ {
+ if (preProcessMarkupInOriginal)
+ source1 = PreProcessMarkup(source1, settings.StartingIdForFootnotesEndnotes + 1000);
+ source2 = PreProcessMarkup(source2, settings.StartingIdForFootnotesEndnotes + 2000);
+
+ if (s_SaveIntermediateFilesForDebugging && settings.DebugTempFileDi != null)
+ {
+ var name1 = "Source1-Step1-PreProcess.docx";
+ var name2 = "Source2-Step1-PreProcess.docx";
+ var preProcFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ source1.SaveAs(preProcFi1.FullName);
+ var preProcFi2 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name2));
+ source2.SaveAs(preProcFi2.FullName);
+ }
+
+ // at this point, both source1 and source2 have unid on every element. These are the values that will enable reassembly of the XML tree.
+ // but we need other values.
+
+ // In source1:
+ // - accept tracked revisions
+ // - determine hash code for every block-level element
+ // - save as attribute on every element
+
+ // - accept tracked revisions and reject tracked revisions leave the unids alone, where possible.
+ // - after accepting and calculating the hash, then can use the unids to find the right block-level element in the unmodified source1, and install the hash
+
+ // In source2:
+ // - reject tracked revisions
+ // - determine hash code for every block-level element
+ // - save as an attribute on every element
+
+ // - after rejecting and calculating the hash, then can use the unids to find the right block-level element in the unmodified source2, and install the hash
+
+ // - sometimes after accepting or rejecting tracked revisions, several paragraphs will get coalesced into a single paragraph due to paragraph marks being inserted / deleted.
+ // - in this case, some paragraphs will not get a hash injected onto them.
+ // - if a paragraph doesn't have a hash, then it will never correspond to another paragraph, and such issues will need to be resolved in the normal execution of the LCS algorithm.
+ // - note that when we do propagate the unid through for the first paragraph.
+
+ // Establish correlation between the two.
+ // Find the longest common sequence of block-level elements where hash codes are the same.
+ // this sometimes will be every block level element in the document. Or sometimes will be just a fair number of them.
+
+ // at the start of doing the LCS algorithm, we will match up content, and put them in corresponding unknown correlated comparison units. Those paragraphs will only ever be matched to their corresponding paragraph.
+ // then the algorithm can proceed as usual.
+
+ // need to call ChangeFootnoteEndnoteReferencesToUniqueRange before creating the wmlResult document, so that
+ // the same GUID ids are used for footnote and endnote references in both the 'after' document, and in the
+ // result document.
+
+ var source1afterAccepting = RevisionProcessor.AcceptRevisions(source1);
+ var source2afterRejecting = RevisionProcessor.RejectRevisions(source2);
+
+ if (s_SaveIntermediateFilesForDebugging && settings.DebugTempFileDi != null)
+ {
+ var name1 = "Source1-Step2-AfterAccepting.docx";
+ var name2 = "Source2-Step2-AfterRejecting.docx";
+ var afterAcceptingFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ source1afterAccepting.SaveAs(afterAcceptingFi1.FullName);
+ var afterRejectingFi2 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name2));
+ source2afterRejecting.SaveAs(afterRejectingFi2.FullName);
+ }
+
+ // this creates the correlated hash codes that enable us to match up ranges of paragraphs based on
+ // accepting in source1, rejecting in source2
+ source1 = HashBlockLevelContent(source1, source1afterAccepting, settings);
+ source2 = HashBlockLevelContent(source2, source2afterRejecting, settings);
+
+ if (s_SaveIntermediateFilesForDebugging && settings.DebugTempFileDi != null)
+ {
+ var name1 = "Source1-Step3-AfterHashing.docx";
+ var name2 = "Source2-Step3-AfterHashing.docx";
+ var afterHashingFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ source1.SaveAs(afterHashingFi1.FullName);
+ var afterHashingFi2 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name2));
+ source2.SaveAs(afterHashingFi2.FullName);
+ }
+
+ // Accept revisions in before, and after
+ source1 = RevisionProcessor.AcceptRevisions(source1);
+ source2 = RevisionProcessor.AcceptRevisions(source2);
+
+ if (s_SaveIntermediateFilesForDebugging && settings.DebugTempFileDi != null)
+ {
+ var name1 = "Source1-Step4-AfterAccepting.docx";
+ var name2 = "Source2-Step4-AfterAccepting.docx";
+ var afterAcceptingFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ source1.SaveAs(afterAcceptingFi1.FullName);
+ var afterAcceptingFi2 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name2));
+ source2.SaveAs(afterAcceptingFi2.FullName);
+ }
+
+ // after accepting revisions, some unids may have been removed by revision accepter, along with the correlatedSHA1Hash codes,
+ // this is as it should be.
+ // but need to go back in and add guids to paragraphs that have had them removed.
+
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(source2.DocumentByteArray, 0, source2.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ AddUnidsToMarkupInContentParts(wDoc);
+ }
+ }
+
+ WmlDocument wmlResult = new WmlDocument(source1);
+ using (MemoryStream ms1 = new MemoryStream())
+ using (MemoryStream ms2 = new MemoryStream())
+ {
+ ms1.Write(source1.DocumentByteArray, 0, source1.DocumentByteArray.Length);
+ ms2.Write(source2.DocumentByteArray, 0, source2.DocumentByteArray.Length);
+ WmlDocument producedDocument;
+ using (WordprocessingDocument wDoc1 = WordprocessingDocument.Open(ms1, true))
+ using (WordprocessingDocument wDoc2 = WordprocessingDocument.Open(ms2, true))
+ {
+ producedDocument = ProduceDocumentWithTrackedRevisions(settings, wmlResult, wDoc1, wDoc2);
+ }
+
+ if (s_False && settings.DebugTempFileDi != null)
+ {
+ var name1 = "Source1-Step5-AfterProducingDocWithRevTrk.docx";
+ var name2 = "Source2-Step5-AfterProducingDocWithRevTrk.docx";
+ var afterProducingFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ var afterProducingWml1 = new WmlDocument("after1.docx", ms1.ToArray());
+ afterProducingWml1.SaveAs(afterProducingFi1.FullName);
+ var afterProducingFi2 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name2));
+ var afterProducingWml2 = new WmlDocument("after2.docx", ms2.ToArray());
+ afterProducingWml2.SaveAs(afterProducingFi2.FullName);
+ }
+
+ if (s_False && settings.DebugTempFileDi != null)
+ {
+ var cleanedSource = CleanPowerToolsAndRsid(source1);
+ var name1 = "Cleaned-Source.docx";
+ var cleanedSourceFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ cleanedSource.SaveAs(cleanedSourceFi1.FullName);
+
+ var cleanedProduced = CleanPowerToolsAndRsid(producedDocument);
+ var name2 = "Cleaned-Produced.docx";
+ var cleanedProducedFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name2));
+ cleanedProduced.SaveAs(cleanedProducedFi1.FullName);
+ }
+
+ return producedDocument;
+ }
+ }
+
+ private static WmlDocument CleanPowerToolsAndRsid(WmlDocument producedDocument)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(producedDocument.DocumentByteArray, 0, producedDocument.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ foreach (var cp in wDoc.ContentParts())
+ {
+ var xd = cp.GetXDocument();
+ var newRoot = CleanPartTransform(xd.Root);
+ xd.Root.ReplaceWith(newRoot);
+ cp.PutXDocument();
+ }
+ }
+ var cleaned = new WmlDocument("cleaned.docx", ms.ToArray());
+ return cleaned;
+ }
+ }
+
+ private static WmlDocument HashBlockLevelContent(WmlDocument source, WmlDocument source1afterProcessingRevTracking, WmlComparerSettings settings)
+ {
+ using (MemoryStream msSource = new MemoryStream())
+ using (MemoryStream msAfterProc = new MemoryStream())
+ {
+ msSource.Write(source.DocumentByteArray, 0, source.DocumentByteArray.Length);
+ msAfterProc.Write(source1afterProcessingRevTracking.DocumentByteArray, 0, source1afterProcessingRevTracking.DocumentByteArray.Length);
+ using (WordprocessingDocument wDocSource = WordprocessingDocument.Open(msSource, true))
+ using (WordprocessingDocument wDocAfterProc = WordprocessingDocument.Open(msAfterProc, true))
+ {
+ // create Unid dictionary for source
+ var sourceMainXDoc = wDocSource
+ .MainDocumentPart
+ .GetXDocument();
+
+ var sourceUnidDict = sourceMainXDoc
+ .Root
+ .Descendants()
+ .Where(d => d.Name == W.p || d.Name == W.tbl || d.Name == W.tr)
+ .ToDictionary(d => (string)d.Attribute(PtOpenXml.Unid));
+
+ var afterProcMainXDoc = wDocAfterProc
+ .MainDocumentPart
+ .GetXDocument();
+
+ foreach (var blockLevelContent in afterProcMainXDoc.Root.Descendants().Where(d => d.Name == W.p || d.Name == W.tbl || d.Name == W.tr))
+ {
+ var cloneBlockLevelContentForHashing = (XElement)CloneBlockLevelContentForHashing(wDocAfterProc.MainDocumentPart, blockLevelContent, true, settings);
+ var shaString = cloneBlockLevelContentForHashing.ToString(SaveOptions.DisableFormatting)
+ .Replace(" xmlns=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"", "");
+ var sha1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(shaString);
+ var thisUnid = (string)blockLevelContent.Attribute(PtOpenXml.Unid);
+ if (thisUnid != null)
+ {
+ if (sourceUnidDict.ContainsKey(thisUnid))
+ {
+ var correlatedBlockLevelContent = sourceUnidDict[thisUnid];
+ correlatedBlockLevelContent.Add(new XAttribute(PtOpenXml.CorrelatedSHA1Hash, sha1Hash));
+ }
+ }
+ }
+
+ wDocSource.MainDocumentPart.PutXDocument();
+ }
+ WmlDocument sourceWithCorrelatedSHA1Hash = new WmlDocument(source.FileName, msSource.ToArray());
+ return sourceWithCorrelatedSHA1Hash;
+ }
+ }
+
+ private static WmlDocument PreProcessMarkup(WmlDocument source, int startingIdForFootnotesEndnotes)
+ {
+ // open and close to get rid of MC content
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(source.DocumentByteArray, 0, source.DocumentByteArray.Length);
+ OpenSettings os = new OpenSettings();
+ os.MarkupCompatibilityProcessSettings = new MarkupCompatibilityProcessSettings(MarkupCompatibilityProcessMode.ProcessAllParts,
+ DocumentFormat.OpenXml.FileFormatVersions.Office2007);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true, os))
+ {
+ var doc = wDoc.MainDocumentPart.RootElement;
+ if (wDoc.MainDocumentPart.FootnotesPart != null)
+ {
+ // contrary to what you might think, looking at the API, it is necessary to access the root element of each part to cause
+ // the SDK to process MC markup.
+ var fn = wDoc.MainDocumentPart.FootnotesPart.RootElement;
+ }
+ if (wDoc.MainDocumentPart.EndnotesPart != null)
+ {
+ var en = wDoc.MainDocumentPart.EndnotesPart.RootElement;
+ }
+ }
+ source = new WmlDocument(source.FileName, ms.ToArray());
+ }
+
+ // open and close to get rid of MC content
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(source.DocumentByteArray, 0, source.DocumentByteArray.Length);
+ OpenSettings os = new OpenSettings();
+ os.MarkupCompatibilityProcessSettings = new MarkupCompatibilityProcessSettings(MarkupCompatibilityProcessMode.ProcessAllParts,
+ DocumentFormat.OpenXml.FileFormatVersions.Office2007);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true, os))
+ {
+ TestForInvalidContent(wDoc);
+ RemoveExistingPowerToolsMarkup(wDoc);
+ SimplifyMarkupSettings msSettings = new SimplifyMarkupSettings()
+ {
+ RemoveBookmarks = true,
+ AcceptRevisions = false,
+ RemoveComments = true,
+ RemoveContentControls = true,
+ RemoveFieldCodes = true,
+ RemoveGoBackBookmark = true,
+ RemoveLastRenderedPageBreak = true,
+ RemovePermissions = true,
+ RemoveProof = true,
+ RemoveSmartTags = true,
+ RemoveSoftHyphens = true,
+ RemoveHyperlinks = true,
+ };
+ MarkupSimplifier.SimplifyMarkup(wDoc, msSettings);
+ ChangeFootnoteEndnoteReferencesToUniqueRange(wDoc, startingIdForFootnotesEndnotes);
+ AddUnidsToMarkupInContentParts(wDoc);
+ AddFootnotesEndnotesParts(wDoc);
+ FillInEmptyFootnotesEndnotes(wDoc);
+ }
+ return new WmlDocument(source.FileName, ms.ToArray());
+ }
+ }
+
+ // somehow, sometimes a footnote or endnote contains absolutely nothing - no paragraph - nothing.
+ // This messes up the algorithm, so in this case, insert an empty paragraph.
+ // This is pretty wacky markup to find, and I don't know how this markup comes into existence, but this is an innocuous fix.
+ private static void FillInEmptyFootnotesEndnotes(WordprocessingDocument wDoc)
+ {
+ XElement emptyFootnote = XElement.Parse(
+@"<w:p xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:pPr>
+ <w:pStyle w:val='FootnoteText'/>
+ </w:pPr>
+ <w:r>
+ <w:rPr>
+ <w:rStyle w:val='FootnoteReference'/>
+ </w:rPr>
+ <w:footnoteRef/>
+ </w:r>
+</w:p>");
+
+ XElement emptyEndnote = XElement.Parse(
+@"<w:p xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>
+ <w:pPr>
+ <w:pStyle w:val='EndnoteText'/>
+ </w:pPr>
+ <w:r>
+ <w:rPr>
+ <w:rStyle w:val='EndnoteReference'/>
+ </w:rPr>
+ <w:endnoteRef/>
+ </w:r>
+</w:p>");
+
+ var footnotePart = wDoc.MainDocumentPart.FootnotesPart;
+ if (footnotePart != null)
+ {
+ var fnXDoc = footnotePart.GetXDocument();
+ foreach (var fn in fnXDoc.Root.Elements(W.footnote))
+ {
+ if (!fn.HasElements)
+ fn.Add(emptyFootnote);
+ }
+ footnotePart.PutXDocument();
+ }
+
+ var endnotePart = wDoc.MainDocumentPart.EndnotesPart;
+ if (endnotePart != null)
+ {
+ var fnXDoc = endnotePart.GetXDocument();
+ foreach (var fn in fnXDoc.Root.Elements(W.endnote))
+ {
+ if (!fn.HasElements)
+ fn.Add(emptyEndnote);
+ }
+ endnotePart.PutXDocument();
+ }
+ }
+
+ private static bool ContentContainsFootnoteEndnoteReferencesThatHaveRevisions(XElement element, WordprocessingDocument wDocDelta)
+ {
+ var footnoteEndnoteReferences = element.Descendants().Where(d => d.Name == W.footnoteReference || d.Name == W.endnoteReference);
+ if (!footnoteEndnoteReferences.Any())
+ return false;
+ var footnoteXDoc = wDocDelta.MainDocumentPart.FootnotesPart.GetXDocument();
+ var endnoteXDoc = wDocDelta.MainDocumentPart.EndnotesPart.GetXDocument();
+ foreach (var note in footnoteEndnoteReferences)
+ {
+ XElement fnen = null;
+ if (note.Name == W.footnoteReference)
+ {
+ var id = (int)note.Attribute(W.id);
+ fnen = footnoteXDoc
+ .Root
+ .Elements(W.footnote)
+ .FirstOrDefault(n => (int)n.Attribute(W.id) == id);
+ if (fnen.Descendants().Where(d => d.Name == W.ins || d.Name == W.del).Any())
+ return true;
+ }
+ if (note.Name == W.endnoteReference)
+ {
+ var id = (int)note.Attribute(W.id);
+ fnen = endnoteXDoc
+ .Root
+ .Elements(W.endnote)
+ .FirstOrDefault(n => (int)n.Attribute(W.id) == id);
+ if (fnen.Descendants().Where(d => d.Name == W.ins || d.Name == W.del).Any())
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void AddUnidsToMarkupInContentParts(WordprocessingDocument wDoc)
+ {
+ var mdp = wDoc.MainDocumentPart.GetXDocument();
+ AssignUnidToAllElements(mdp.Root);
+ IgnorePt14Namespace(mdp.Root);
+ wDoc.MainDocumentPart.PutXDocument();
+
+ if (wDoc.MainDocumentPart.FootnotesPart != null)
+ {
+ var p = wDoc.MainDocumentPart.FootnotesPart.GetXDocument();
+ AssignUnidToAllElements(p.Root);
+ IgnorePt14Namespace(p.Root);
+ wDoc.MainDocumentPart.FootnotesPart.PutXDocument();
+ }
+
+ if (wDoc.MainDocumentPart.EndnotesPart != null)
+ {
+ var p = wDoc.MainDocumentPart.EndnotesPart.GetXDocument();
+ AssignUnidToAllElements(p.Root);
+ IgnorePt14Namespace(p.Root);
+ wDoc.MainDocumentPart.EndnotesPart.PutXDocument();
+ }
+ }
+
+ private class ConsolidationInfo
+ {
+ public string Revisor;
+ public Color Color;
+ public XElement RevisionElement;
+ public bool InsertBefore = false;
+ public string RevisionHash;
+ public XElement[] Footnotes;
+ public XElement[] Endnotes;
+ public string RevisionString; // for debugging purposes only
+ }
+
+ private static string nl = Environment.NewLine;
+
+ /*****************************************************************************************************************/
+ // Consolidate processes footnotes and endnotes in a particular fashion - if the unmodified document has a footnote
+ // reference, and a delta has a footnote reference, we end up with two footnotes - one is unmodified, and is refered to
+ // from the unmodified content. The footnote reference in the delta refers to the modified footnote. This is as it
+ // should be.
+ /*****************************************************************************************************************/
+ public static WmlDocument Consolidate(WmlDocument original,
+ List<WmlRevisedDocumentInfo> revisedDocumentInfoList,
+ WmlComparerSettings settings)
+ {
+ var consolidateSettings = new WmlComparerConsolidateSettings();
+ return Consolidate(original, revisedDocumentInfoList, settings, consolidateSettings);
+ }
+
+ public static WmlDocument Consolidate(WmlDocument original,
+ List<WmlRevisedDocumentInfo> revisedDocumentInfoList,
+ WmlComparerSettings settings, WmlComparerConsolidateSettings consolidateSettings)
+ {
+ // pre-process the original, so that it already has unids for all elements
+ // then when comparing all documents to the original, each one will have the unid as appropriate
+ // for all revision block-level content
+ // set unid to look for
+ // while true
+ // determine where to insert
+ // get the unid for the revision
+ // look it up in the original. if find it, then insert after that element
+ // if not in the original
+ // look backwards in revised document, set unid to look for, do the loop again
+ // if get to the beginning of the document
+ // insert at beginning of document
+
+ settings.StartingIdForFootnotesEndnotes = 3000;
+ var originalWithUnids = PreProcessMarkup(original, settings.StartingIdForFootnotesEndnotes);
+ WmlDocument consolidated = new WmlDocument(originalWithUnids);
+
+ if (s_SaveIntermediateFilesForDebugging && settings.DebugTempFileDi != null)
+ {
+ var name1 = "Original-with-Unids.docx";
+ var preProcFi1 = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ originalWithUnids.SaveAs(preProcFi1.FullName);
+ }
+
+ var revisedDocumentInfoListCount = revisedDocumentInfoList.Count();
+
+ using (MemoryStream consolidatedMs = new MemoryStream())
+ {
+ consolidatedMs.Write(consolidated.DocumentByteArray, 0, consolidated.DocumentByteArray.Length);
+ using (WordprocessingDocument consolidatedWDoc = WordprocessingDocument.Open(consolidatedMs, true))
+ {
+ var consolidatedMainDocPart = consolidatedWDoc.MainDocumentPart;
+ var consolidatedMainDocPartXDoc = consolidatedMainDocPart.GetXDocument();
+
+ // save away last sectPr
+ XElement savedSectPr = consolidatedMainDocPartXDoc
+ .Root
+ .Element(W.body)
+ .Elements(W.sectPr)
+ .LastOrDefault();
+ consolidatedMainDocPartXDoc
+ .Root
+ .Element(W.body)
+ .Elements(W.sectPr)
+ .Remove();
+
+ var consolidatedByUnid = consolidatedMainDocPartXDoc
+ .Descendants()
+ .Where(d => (d.Name == W.p || d.Name == W.tbl) && d.Attribute(PtOpenXml.Unid) != null)
+ .ToDictionary(d => (string)d.Attribute(PtOpenXml.Unid));
+
+ int deltaNbr = 1;
+ foreach (var revisedDocumentInfo in revisedDocumentInfoList)
+ {
+ settings.StartingIdForFootnotesEndnotes = (deltaNbr * 2000) + 3000;
+ var delta = WmlComparer.CompareInternal(originalWithUnids, revisedDocumentInfo.RevisedDocument, settings, false);
+
+ if (s_SaveIntermediateFilesForDebugging && settings.DebugTempFileDi != null)
+ {
+ var name1 = string.Format("Delta-{0}.docx", deltaNbr++);
+ var deltaFi = new FileInfo(Path.Combine(settings.DebugTempFileDi.FullName, name1));
+ delta.SaveAs(deltaFi.FullName);
+ }
+
+ var colorRgb = revisedDocumentInfo.Color.ToArgb();
+ var colorString = colorRgb.ToString("X");
+ if (colorString.Length == 8)
+ colorString = colorString.Substring(2);
+
+ using (MemoryStream msOriginalWithUnids = new MemoryStream())
+ using (MemoryStream msDelta = new MemoryStream())
+ {
+ msOriginalWithUnids.Write(originalWithUnids.DocumentByteArray, 0, originalWithUnids.DocumentByteArray.Length);
+ msDelta.Write(delta.DocumentByteArray, 0, delta.DocumentByteArray.Length);
+ using (WordprocessingDocument wDocOriginalWithUnids = WordprocessingDocument.Open(msOriginalWithUnids, true))
+ using (WordprocessingDocument wDocDelta = WordprocessingDocument.Open(msDelta, true))
+ {
+ var modMainDocPart = wDocDelta.MainDocumentPart;
+ var modMainDocPartXDoc = modMainDocPart.GetXDocument();
+ var blockLevelContentToMove = modMainDocPartXDoc
+ .Root
+ .DescendantsTrimmed(d => d.Name == W.txbxContent || d.Name == W.tr)
+ .Where(d => d.Name == W.p || d.Name == W.tbl)
+ .Where(d => d.Descendants().Any(z => z.Name == W.ins || z.Name == W.del) ||
+ ContentContainsFootnoteEndnoteReferencesThatHaveRevisions(d, wDocDelta))
+ .ToList();
+
+ foreach (var revision in blockLevelContentToMove)
+ {
+ var elementLookingAt = revision;
+ while (true)
+ {
+ var unid = (string)elementLookingAt.Attribute(PtOpenXml.Unid);
+ if (unid == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ XElement elementToInsertAfter = null;
+ if (consolidatedByUnid.ContainsKey(unid))
+ elementToInsertAfter = consolidatedByUnid[unid];
+
+ if (elementToInsertAfter != null)
+ {
+ ConsolidationInfo ci = new ConsolidationInfo();
+ ci.Revisor = revisedDocumentInfo.Revisor;
+ ci.Color = revisedDocumentInfo.Color;
+ ci.RevisionElement = revision;
+ ci.Footnotes = revision
+ .Descendants(W.footnoteReference)
+ .Select(fr =>
+ {
+ var id = (int)fr.Attribute(W.id);
+ var fnXDoc = wDocDelta.MainDocumentPart.FootnotesPart.GetXDocument();
+ var footnote = fnXDoc.Root.Elements(W.footnote).FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ if (footnote == null)
+ throw new OpenXmlPowerToolsException("Internal Error");
+ return footnote;
+ })
+ .ToArray();
+ ci.Endnotes = revision
+ .Descendants(W.endnoteReference)
+ .Select(er =>
+ {
+ var id = (int)er.Attribute(W.id);
+ var enXDoc = wDocDelta.MainDocumentPart.EndnotesPart.GetXDocument();
+ var endnote = enXDoc.Root.Elements(W.endnote).FirstOrDefault(en => (int)en.Attribute(W.id) == id);
+ if (endnote == null)
+ throw new OpenXmlPowerToolsException("Internal Error");
+ return endnote;
+ })
+ .ToArray();
+ AddToAnnotation(
+ wDocDelta,
+ consolidatedWDoc,
+ elementToInsertAfter,
+ ci,
+ settings);
+ break;
+ }
+ else
+ {
+ // find an element to insert after
+ var elementBeforeRevision = elementLookingAt
+ .SiblingsBeforeSelfReverseDocumentOrder()
+ .FirstOrDefault(e => e.Attribute(PtOpenXml.Unid) != null);
+ if (elementBeforeRevision == null)
+ {
+ var firstElement = consolidatedMainDocPartXDoc
+ .Root
+ .Element(W.body)
+ .Elements()
+ .FirstOrDefault(e => e.Name == W.p || e.Name == W.tbl);
+
+ ConsolidationInfo ci = new ConsolidationInfo();
+ ci.Revisor = revisedDocumentInfo.Revisor;
+ ci.Color = revisedDocumentInfo.Color;
+ ci.RevisionElement = revision;
+ ci.InsertBefore = true;
+ ci.Footnotes = revision
+ .Descendants(W.footnoteReference)
+ .Select(fr =>
+ {
+ var id = (int)fr.Attribute(W.id);
+ var fnXDoc = wDocDelta.MainDocumentPart.FootnotesPart.GetXDocument();
+ var footnote = fnXDoc.Root.Elements(W.footnote).FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ if (footnote == null)
+ throw new OpenXmlPowerToolsException("Internal Error");
+ return footnote;
+ })
+ .ToArray();
+ ci.Endnotes = revision
+ .Descendants(W.endnoteReference)
+ .Select(er =>
+ {
+ var id = (int)er.Attribute(W.id);
+ var enXDoc = wDocDelta.MainDocumentPart.EndnotesPart.GetXDocument();
+ var endnote = enXDoc.Root.Elements(W.endnote).FirstOrDefault(en => (int)en.Attribute(W.id) == id);
+ if (endnote == null)
+ throw new OpenXmlPowerToolsException("Internal Error");
+ return endnote;
+ })
+ .ToArray();
+ AddToAnnotation(
+ wDocDelta,
+ consolidatedWDoc,
+ firstElement,
+ ci,
+ settings);
+ break;
+ }
+ else
+ {
+ elementLookingAt = elementBeforeRevision;
+ continue;
+ }
+ }
+ }
+ }
+ CopyMissingStylesFromOneDocToAnother(wDocDelta, consolidatedWDoc);
+ }
+ }
+ }
+
+ // at this point, everything is added as an annotation, from all documents to be merged.
+ // so now the process is to go through and add the annotations to the document
+ var elementsToProcess = consolidatedMainDocPartXDoc
+ .Root
+ .Descendants()
+ .Where(d => d.Annotation<List<ConsolidationInfo>>() != null)
+ .ToList();
+
+ var emptyParagraph = new XElement(W.p,
+ new XElement(W.pPr,
+ new XElement(W.spacing,
+ new XAttribute(W.after, "0"),
+ new XAttribute(W.line, "240"),
+ new XAttribute(W.lineRule, "auto"))));
+
+ foreach (var ele in elementsToProcess)
+ {
+ var lci = ele.Annotation<List<ConsolidationInfo>>();
+
+ // process before
+ var contentToAddBefore = lci
+ .Where(ci => ci.InsertBefore == true)
+ .GroupAdjacent(ci => ci.Revisor + ci.Color.ToString())
+ .Select((groupedCi, idx) => AssembledConjoinedRevisionContent(emptyParagraph, groupedCi, idx, consolidatedWDoc, consolidateSettings));
+ ele.AddBeforeSelf(contentToAddBefore);
+
+ // process after
+ // if all revisions from all revisors are exactly the same, then instead of adding multiple tables after
+ // that contains the revisions, then simply replace the paragraph with the one with the revisions.
+ // RC004 documents contain the test data to exercise this.
+
+ var lciCount = lci.Where(ci => ci.InsertBefore == false).Count();
+
+ if (lciCount > 1 && lciCount == revisedDocumentInfoListCount)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // This is the code that determines if revisions should be consolidated into one.
+
+ var uniqueRevisions = lci
+ .Where(ci => ci.InsertBefore == false)
+ .GroupBy(ci =>
+ {
+ // Get a hash after first accepting revisions and compressing the text.
+ var acceptedRevisionElement = RevisionProcessor.AcceptRevisionsForElement(ci.RevisionElement);
+ var sha1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(acceptedRevisionElement.Value.Replace(" ", "").Replace(" ", "").Replace(" ", "").Replace("\n", "").Replace(".", "").Replace(",", "").ToUpper());
+ return sha1Hash;
+ })
+ .OrderByDescending(g => g.Count())
+ .ToList();
+ var uniqueRevisionCount = uniqueRevisions.Count();
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ if (uniqueRevisionCount == 1)
+ {
+ MoveFootnotesEndnotesForConsolidatedRevisions(lci.First(), consolidatedWDoc);
+
+ var dummyElement = new XElement("dummy", lci.First().RevisionElement);
+
+ foreach (var rev in dummyElement.Descendants().Where(d => d.Attribute(W.author) != null))
+ {
+ var aut = rev.Attribute(W.author);
+ aut.Value = "ITU";
+ }
+
+ ele.ReplaceWith(dummyElement.Elements());
+ continue;
+ }
+
+ // this is the location where we have determined that there are the same number of revisions for this paragraph as there are revision documents.
+ // however, the hash for all of them were not the same.
+ // therefore, they would be added to the consolidated document as separate revisions.
+
+ // create a log that shows what is different, in detail.
+ if (settings.LogCallback != null)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("====================================================================================================" + nl);
+ sb.Append("Non-Consolidated Revision" + nl);
+ sb.Append("====================================================================================================" + nl);
+ foreach (var urList in uniqueRevisions)
+ {
+ var revisorList = urList.Select(ur => ur.Revisor + " : ").StringConcatenate().TrimEnd(' ', ':');
+ sb.Append("Revisors: " + revisorList + nl);
+ var str = RevisionToLogFormTransform(urList.First().RevisionElement, 0, false);
+ sb.Append(str);
+ sb.Append("=========================" + nl);
+ }
+ sb.Append(nl);
+ settings.LogCallback(sb.ToString());
+ }
+ }
+
+ var contentToAddAfter = lci
+ .Where(ci => ci.InsertBefore == false)
+ .GroupAdjacent(ci => ci.Revisor + ci.Color.ToString())
+ .Select((groupedCi, idx) => AssembledConjoinedRevisionContent(emptyParagraph, groupedCi, idx, consolidatedWDoc, consolidateSettings));
+ ele.AddAfterSelf(contentToAddAfter);
+ }
+
+#if false
+ // old code
+ foreach (var ele in elementsToProcess)
+ {
+ var lci = ele.Annotation<List<ConsolidationInfo>>();
+
+ // if all revisions from all revisors are exactly the same, then instead of adding multiple tables after
+ // that contains the revisions, then simply replace the paragraph with the one with the revisions.
+ // RC004 documents contain the test data to exercise this.
+
+ var lciCount = lci.Count();
+
+ if (lci.Count() > 1 && lciCount == revisedDocumentInfoListCount)
+ {
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // This is the code that determines if revisions should be consolidated into one.
+
+ var uniqueRevisions = lci
+ .GroupBy(ci =>
+ {
+ // Get a hash after first accepting revisions and compressing the text.
+ var ciz = ci;
+
+ var acceptedRevisionElement = RevisionProcessor.AcceptRevisionsForElement(ci.RevisionElement);
+ var text = acceptedRevisionElement.Value
+ .Replace(" ", "")
+ .Replace(" ", "")
+ .Replace(" ", "")
+ .Replace("\n", "");
+ var sha1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(text);
+ return ci.InsertBefore.ToString() + sha1Hash;
+ })
+ .OrderByDescending(g => g.Count())
+ .ToList();
+ var uniqueRevisionCount = uniqueRevisions.Count();
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ if (uniqueRevisionCount == 1)
+ {
+ MoveFootnotesEndnotesForConsolidatedRevisions(lci.First(), consolidatedWDoc);
+
+ var dummyElement = new XElement("dummy", lci.First().RevisionElement);
+
+ foreach(var rev in dummyElement.Descendants().Where(d => d.Attribute(W.author) != null))
+ {
+ var aut = rev.Attribute(W.author);
+ aut.Value = "ITU";
+ }
+
+ ele.ReplaceWith(dummyElement.Elements());
+ continue;
+ }
+
+ // this is the location where we have determined that there are the same number of revisions for this paragraph as there are revision documents.
+ // however, the hash for all of them were not the same.
+ // therefore, they would be added to the consolidated document as separate revisions.
+
+ // create a log that shows what is different, in detail.
+ if (settings.LogCallback != null)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("====================================================================================================" + nl);
+ sb.Append("Non-Consolidated Revision" + nl);
+ sb.Append("====================================================================================================" + nl);
+ foreach (var urList in uniqueRevisions)
+ {
+ var revisorList = urList.Select(ur => ur.Revisor + " : ").StringConcatenate().TrimEnd(' ', ':');
+ sb.Append("Revisors: " + revisorList + nl);
+ var str = RevisionToLogFormTransform(urList.First().RevisionElement, 0, false);
+ sb.Append(str);
+ sb.Append("=========================" + nl);
+ }
+ sb.Append(nl);
+ settings.LogCallback(sb.ToString());
+ }
+ }
+
+ var contentToAddBefore = lci
+ .Where(ci => ci.InsertBefore == true)
+ .GroupAdjacent(ci => ci.Revisor + ci.Color.ToString())
+ .Select((groupedCi, idx) => AssembledConjoinedRevisionContent(emptyParagraph, groupedCi, idx, consolidatedWDoc, consolidateSettings));
+ var contentToAddAfter = lci
+ .Where(ci => ci.InsertBefore == false)
+ .GroupAdjacent(ci => ci.Revisor + ci.Color.ToString())
+ .Select((groupedCi, idx) => AssembledConjoinedRevisionContent(emptyParagraph, groupedCi, idx, consolidatedWDoc, consolidateSettings));
+ ele.AddBeforeSelf(contentToAddBefore);
+ ele.AddAfterSelf(contentToAddAfter);
+ }
+#endif
+
+ consolidatedMainDocPartXDoc
+ .Root
+ .Element(W.body)
+ .Add(savedSectPr);
+
+ AddTableGridStyleToStylesPart(consolidatedWDoc.MainDocumentPart.StyleDefinitionsPart);
+ FixUpRevisionIds(consolidatedWDoc, consolidatedMainDocPartXDoc);
+ IgnorePt14NamespaceForFootnotesEndnotes(consolidatedWDoc);
+ FixUpDocPrIds(consolidatedWDoc);
+ FixUpShapeIds(consolidatedWDoc);
+ FixUpGroupIds(consolidatedWDoc);
+ FixUpShapeTypeIds(consolidatedWDoc);
+ WmlComparer.IgnorePt14Namespace(consolidatedMainDocPartXDoc.Root);
+ consolidatedWDoc.MainDocumentPart.PutXDocument();
+ AddFootnotesEndnotesStyles(consolidatedWDoc);
+ }
+
+ var newConsolidatedDocument = new WmlDocument("consolidated.docx", consolidatedMs.ToArray());
+ return newConsolidatedDocument;
+ }
+ }
+
+ private static void MoveFootnotesEndnotesForConsolidatedRevisions(ConsolidationInfo ci, WordprocessingDocument wDocConsolidated)
+ {
+ var consolidatedFootnoteXDoc = wDocConsolidated.MainDocumentPart.FootnotesPart.GetXDocument();
+ var consolidatedEndnoteXDoc = wDocConsolidated.MainDocumentPart.EndnotesPart.GetXDocument();
+
+ int maxFootnoteId = 1;
+ if (consolidatedFootnoteXDoc.Root.Elements(W.footnote).Any())
+ maxFootnoteId = consolidatedFootnoteXDoc.Root.Elements(W.footnote).Select(e => (int)e.Attribute(W.id)).Max();
+ int maxEndnoteId = 1;
+ if (consolidatedEndnoteXDoc.Root.Elements(W.endnote).Any())
+ maxEndnoteId = consolidatedEndnoteXDoc.Root.Elements(W.endnote).Select(e => (int)e.Attribute(W.id)).Max(); ;
+
+ /// At this point, content might contain a footnote or endnote reference.
+ /// Need to add the footnote / endnote into the consolidated document (with the same guid id)
+ /// Because of preprocessing of the documents, all footnote and endnote references will be unique at this point
+
+ if (ci.RevisionElement.Descendants(W.footnoteReference).Any())
+ {
+ var footnoteXDoc = wDocConsolidated.MainDocumentPart.FootnotesPart.GetXDocument();
+ foreach (var footnoteReference in ci.RevisionElement.Descendants(W.footnoteReference))
+ {
+ var id = (int)footnoteReference.Attribute(W.id);
+ var footnote = ci.Footnotes.FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ var newId = maxFootnoteId + 1;
+ maxFootnoteId++;
+ footnoteReference.Attribute(W.id).Value = newId.ToString();
+ var clonedFootnote = new XElement(footnote);
+ clonedFootnote.Attribute(W.id).Value = newId.ToString();
+ footnoteXDoc.Root.Add(clonedFootnote);
+ }
+ wDocConsolidated.MainDocumentPart.FootnotesPart.PutXDocument();
+ }
+
+ if (ci.RevisionElement.Descendants(W.endnoteReference).Any())
+ {
+ var endnoteXDoc = wDocConsolidated.MainDocumentPart.EndnotesPart.GetXDocument();
+ foreach (var endnoteReference in ci.RevisionElement.Descendants(W.endnoteReference))
+ {
+ var id = (int)endnoteReference.Attribute(W.id);
+ var endnote = ci.Endnotes.FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ var newId = maxEndnoteId + 1;
+ maxEndnoteId++;
+ endnoteReference.Attribute(W.id).Value = newId.ToString();
+ var clonedEndnote = new XElement(endnote);
+ clonedEndnote.Attribute(W.id).Value = newId.ToString();
+ endnoteXDoc.Root.Add(clonedEndnote);
+ }
+ wDocConsolidated.MainDocumentPart.EndnotesPart.PutXDocument();
+ }
+ }
+
+ private static object CleanPartTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name,
+ element.Attributes().Where(a => a.Name.Namespace != PtOpenXml.pt &&
+ !a.Name.LocalName.ToLower().Contains("rsid")),
+ element.Nodes().Select(n => CleanPartTransform(n)));
+ }
+ return node;
+ }
+
+ private static string RevisionToLogFormTransform(XElement element, int depth, bool inserting)
+ {
+ if (element.Name == W.p)
+ return "Paragraph" + nl + element.Elements().Select(e => RevisionToLogFormTransform(e, depth + 2, false)).StringConcatenate();
+ if (element.Name == W.pPr || element.Name == W.rPr)
+ return "";
+ if (element.Name == W.r)
+ return element.Elements().Select(e => RevisionToLogFormTransform(e, depth, inserting)).StringConcatenate();
+ if (element.Name == W.t)
+ {
+ if (inserting)
+ return "".PadRight(depth) + "Inserted Text:" + QuoteIt((string)element) + nl;
+ else
+ return "".PadRight(depth) + "Text:" + QuoteIt((string)element) + nl;
+ }
+ if (element.Name == W.delText)
+ return "".PadRight(depth) + "Deleted Text:" + QuoteIt((string)element) + nl;
+ if (element.Name == W.ins)
+ return element.Elements().Select(e => RevisionToLogFormTransform(e, depth, true)).StringConcatenate();
+ if (element.Name == W.del)
+ return element.Elements().Select(e => RevisionToLogFormTransform(e, depth, false)).StringConcatenate();
+ return "";
+ }
+
+ private static string QuoteIt(string str)
+ {
+ var quoteString = "\"";
+ if (str.Contains('\"'))
+ quoteString = "\'";
+ return quoteString + str + quoteString;
+ }
+
+ private static void IgnorePt14NamespaceForFootnotesEndnotes(WordprocessingDocument wDoc)
+ {
+ var footnotesPart = wDoc.MainDocumentPart.FootnotesPart;
+ var endnotesPart = wDoc.MainDocumentPart.EndnotesPart;
+
+ XDocument footnotesPartXDoc = null;
+ if (footnotesPart != null)
+ {
+ footnotesPartXDoc = footnotesPart.GetXDocument();
+ WmlComparer.IgnorePt14Namespace(footnotesPartXDoc.Root);
+ }
+
+ XDocument endnotesPartXDoc = null;
+ if (endnotesPart != null)
+ {
+ endnotesPartXDoc = endnotesPart.GetXDocument();
+ WmlComparer.IgnorePt14Namespace(endnotesPartXDoc.Root);
+ }
+
+ if (footnotesPart != null)
+ footnotesPart.PutXDocument();
+
+ if (endnotesPart != null)
+ endnotesPart.PutXDocument();
+ }
+
+ private static XElement[] AssembledConjoinedRevisionContent(XElement emptyParagraph, IGrouping<string, ConsolidationInfo> groupedCi, int idx, WordprocessingDocument wDocConsolidated,
+ WmlComparerConsolidateSettings consolidateSettings)
+ {
+ var consolidatedFootnoteXDoc = wDocConsolidated.MainDocumentPart.FootnotesPart.GetXDocument();
+ var consolidatedEndnoteXDoc = wDocConsolidated.MainDocumentPart.EndnotesPart.GetXDocument();
+
+ int maxFootnoteId = 1;
+ if (consolidatedFootnoteXDoc.Root.Elements(W.footnote).Any())
+ maxFootnoteId = consolidatedFootnoteXDoc.Root.Elements(W.footnote).Select(e => (int)e.Attribute(W.id)).Max();
+ int maxEndnoteId = 1;
+ if (consolidatedEndnoteXDoc.Root.Elements(W.endnote).Any())
+ maxEndnoteId = consolidatedEndnoteXDoc.Root.Elements(W.endnote).Select(e => (int)e.Attribute(W.id)).Max(); ;
+
+ var revisor = groupedCi.First().Revisor;
+
+ var captionParagraph = new XElement(W.p,
+ new XElement(W.pPr,
+ new XElement(W.jc, new XAttribute(W.val, "both")),
+ new XElement(W.rPr,
+ new XElement(W.b),
+ new XElement(W.bCs))),
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.b),
+ new XElement(W.bCs)),
+ new XElement(W.t, revisor)));
+
+ var colorRgb = groupedCi.First().Color.ToArgb();
+ var colorString = colorRgb.ToString("X");
+ if (colorString.Length == 8)
+ colorString = colorString.Substring(2);
+
+ if (consolidateSettings.ConsolidateWithTable)
+ {
+ var table = new XElement(W.tbl,
+ new XElement(W.tblPr,
+ new XElement(W.tblStyle, new XAttribute(W.val, "TableGridForRevisions")),
+ new XElement(W.tblW,
+ new XAttribute(W._w, "0"),
+ new XAttribute(W.type, "auto")),
+ new XElement(W.shd,
+ new XAttribute(W.val, "clear"),
+ new XAttribute(W.color, "auto"),
+ new XAttribute(W.fill, colorString)),
+ new XElement(W.tblLook,
+ new XAttribute(W.firstRow, "0"),
+ new XAttribute(W.lastRow, "0"),
+ new XAttribute(W.firstColumn, "0"),
+ new XAttribute(W.lastColumn, "0"),
+ new XAttribute(W.noHBand, "0"),
+ new XAttribute(W.noVBand, "0"))),
+ new XElement(W.tblGrid,
+ new XElement(W.gridCol, new XAttribute(W._w, "9576"))),
+ new XElement(W.tr,
+ new XElement(W.tc,
+ new XElement(W.tcPr,
+ new XElement(W.shd,
+ new XAttribute(W.val, "clear"),
+ new XAttribute(W.color, "auto"),
+ new XAttribute(W.fill, colorString))),
+ captionParagraph,
+ groupedCi.Select(ci =>
+ {
+ XElement paraAfter = null;
+ if (ci.RevisionElement.Name == W.tbl)
+ paraAfter = emptyParagraph;
+ var revisionInTable = new[] {
+ ci.RevisionElement,
+ paraAfter,
+ };
+
+ /// At this point, content might contain a footnote or endnote reference.
+ /// Need to add the footnote / endnote into the consolidated document (with the same guid id)
+ /// Because of preprocessing of the documents, all footnote and endnote references will be unique at this point
+
+ if (ci.RevisionElement.Descendants(W.footnoteReference).Any())
+ {
+ var footnoteXDoc = wDocConsolidated.MainDocumentPart.FootnotesPart.GetXDocument();
+ foreach (var footnoteReference in ci.RevisionElement.Descendants(W.footnoteReference))
+ {
+ var id = (int)footnoteReference.Attribute(W.id);
+ var footnote = ci.Footnotes.FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ var newId = maxFootnoteId + 1;
+ maxFootnoteId++;
+ footnoteReference.Attribute(W.id).Value = newId.ToString();
+ var clonedFootnote = new XElement(footnote);
+ clonedFootnote.Attribute(W.id).Value = newId.ToString();
+ footnoteXDoc.Root.Add(clonedFootnote);
+ }
+ wDocConsolidated.MainDocumentPart.FootnotesPart.PutXDocument();
+ }
+
+ if (ci.RevisionElement.Descendants(W.endnoteReference).Any())
+ {
+ var endnoteXDoc = wDocConsolidated.MainDocumentPart.EndnotesPart.GetXDocument();
+ foreach (var endnoteReference in ci.RevisionElement.Descendants(W.endnoteReference))
+ {
+ var id = (int)endnoteReference.Attribute(W.id);
+ var endnote = ci.Endnotes.FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ var newId = maxEndnoteId + 1;
+ maxEndnoteId++;
+ endnoteReference.Attribute(W.id).Value = newId.ToString();
+ var clonedEndnote = new XElement(endnote);
+ clonedEndnote.Attribute(W.id).Value = newId.ToString();
+ endnoteXDoc.Root.Add(clonedEndnote);
+ }
+ wDocConsolidated.MainDocumentPart.EndnotesPart.PutXDocument();
+ }
+
+ return revisionInTable;
+ }))));
+
+ // if the last paragraph has a deleted paragraph mark, then remove the deletion from the paragraph mark. This is to prevent Word from misbehaving.
+ // the last paragraph in a cell must not have a deleted paragraph mark.
+ var theCell = table
+ .Descendants(W.tc)
+ .FirstOrDefault();
+ var lastPara = theCell
+ .Elements(W.p)
+ .LastOrDefault();
+ if (lastPara != null)
+ {
+ var isDeleted = lastPara
+ .Elements(W.pPr)
+ .Elements(W.rPr)
+ .Elements(W.del)
+ .Any();
+ if (isDeleted)
+ lastPara
+ .Elements(W.pPr)
+ .Elements(W.rPr)
+ .Elements(W.del)
+ .Remove();
+ }
+
+ var content = new[] {
+ idx == 0 ? emptyParagraph : null,
+ table,
+ emptyParagraph,
+ };
+ return content;
+ }
+ else
+ {
+ var content = groupedCi.Select(ci =>
+ {
+ XElement paraAfter = null;
+ if (ci.RevisionElement.Name == W.tbl)
+ paraAfter = emptyParagraph;
+ var revisionInTable = new[] {
+ ci.RevisionElement,
+ paraAfter,
+ };
+
+ /// At this point, content might contain a footnote or endnote reference.
+ /// Need to add the footnote / endnote into the consolidated document (with the same guid id)
+ /// Because of preprocessing of the documents, all footnote and endnote references will be unique at this point
+
+ if (ci.RevisionElement.Descendants(W.footnoteReference).Any())
+ {
+ var footnoteXDoc = wDocConsolidated.MainDocumentPart.FootnotesPart.GetXDocument();
+ foreach (var footnoteReference in ci.RevisionElement.Descendants(W.footnoteReference))
+ {
+ var id = (int)footnoteReference.Attribute(W.id);
+ var footnote = ci.Footnotes.FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ var newId = maxFootnoteId + 1;
+ maxFootnoteId++;
+ footnoteReference.Attribute(W.id).Value = newId.ToString();
+ var clonedFootnote = new XElement(footnote);
+ clonedFootnote.Attribute(W.id).Value = newId.ToString();
+ footnoteXDoc.Root.Add(clonedFootnote);
+ }
+ wDocConsolidated.MainDocumentPart.FootnotesPart.PutXDocument();
+ }
+
+ if (ci.RevisionElement.Descendants(W.endnoteReference).Any())
+ {
+ var endnoteXDoc = wDocConsolidated.MainDocumentPart.EndnotesPart.GetXDocument();
+ foreach (var endnoteReference in ci.RevisionElement.Descendants(W.endnoteReference))
+ {
+ var id = (int)endnoteReference.Attribute(W.id);
+ var endnote = ci.Endnotes.FirstOrDefault(fn => (int)fn.Attribute(W.id) == id);
+ var newId = maxEndnoteId + 1;
+ maxEndnoteId++;
+ endnoteReference.Attribute(W.id).Value = newId.ToString();
+ var clonedEndnote = new XElement(endnote);
+ clonedEndnote.Attribute(W.id).Value = newId.ToString();
+ endnoteXDoc.Root.Add(clonedEndnote);
+ }
+ wDocConsolidated.MainDocumentPart.EndnotesPart.PutXDocument();
+ }
+
+ return revisionInTable;
+ });
+
+ var dummyElement = new XElement("dummy",
+ content.SelectMany(m => m));
+
+ foreach (var rev in dummyElement.Descendants().Where(d => d.Attribute(W.author) != null))
+ {
+ var aut = rev.Attribute(W.author);
+ aut.Value = revisor;
+ }
+
+ return dummyElement.Elements().ToArray();
+ }
+ }
+
+ private static void AddToAnnotation(
+ WordprocessingDocument wDocDelta,
+ WordprocessingDocument consolidatedWDoc,
+ XElement elementToInsertAfter,
+ ConsolidationInfo consolidationInfo,
+ WmlComparerSettings settings)
+ {
+ Package packageOfDeletedContent = wDocDelta.MainDocumentPart.OpenXmlPackage.Package;
+ Package packageOfNewContent = consolidatedWDoc.MainDocumentPart.OpenXmlPackage.Package;
+ PackagePart partInDeletedDocument = packageOfDeletedContent.GetPart(wDocDelta.MainDocumentPart.Uri);
+ PackagePart partInNewDocument = packageOfNewContent.GetPart(consolidatedWDoc.MainDocumentPart.Uri);
+ consolidationInfo.RevisionElement = MoveRelatedPartsToDestination(partInDeletedDocument, partInNewDocument, consolidationInfo.RevisionElement);
+
+ var clonedForHashing = (XElement)CloneBlockLevelContentForHashing(consolidatedWDoc.MainDocumentPart, consolidationInfo.RevisionElement, false, settings);
+ clonedForHashing.Descendants().Where(d => d.Name == W.ins || d.Name == W.del).Attributes(W.id).Remove();
+ var shaString = clonedForHashing.ToString(SaveOptions.DisableFormatting)
+ .Replace(" xmlns=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"", "");
+ var sha1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(shaString);
+ consolidationInfo.RevisionString = shaString;
+ consolidationInfo.RevisionHash = sha1Hash;
+
+ var annotationList = elementToInsertAfter.Annotation<List<ConsolidationInfo>>();
+ if (annotationList == null)
+ {
+ annotationList = new List<ConsolidationInfo>();
+ elementToInsertAfter.AddAnnotation(annotationList);
+ }
+ annotationList.Add(consolidationInfo);
+ }
+
+ private static void AddTableGridStyleToStylesPart(StyleDefinitionsPart styleDefinitionsPart)
+ {
+ var sXDoc = styleDefinitionsPart.GetXDocument();
+ var tableGridStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "TableGridForRevisions");
+ if (tableGridStyle == null)
+ {
+ var tableGridForRevisionsStyleMarkup =
+@"<w:style w:type=""table""
+ w:styleId=""TableGridForRevisions""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""Table Grid For Revisions""/>
+ <w:basedOn w:val=""TableNormal""/>
+ <w:rsid w:val=""0092121A""/>
+ <w:rPr>
+ <w:rFonts w:asciiTheme=""minorHAnsi""
+ w:eastAsiaTheme=""minorEastAsia""
+ w:hAnsiTheme=""minorHAnsi""
+ w:cstheme=""minorBidi""/>
+ <w:sz w:val=""22""/>
+ <w:szCs w:val=""22""/>
+ </w:rPr>
+ <w:tblPr>
+ <w:tblBorders>
+ <w:top w:val=""single""
+ w:sz=""4""
+ w:space=""0""
+ w:color=""auto""/>
+ <w:left w:val=""single""
+ w:sz=""4""
+ w:space=""0""
+ w:color=""auto""/>
+ <w:bottom w:val=""single""
+ w:sz=""4""
+ w:space=""0""
+ w:color=""auto""/>
+ <w:right w:val=""single""
+ w:sz=""4""
+ w:space=""0""
+ w:color=""auto""/>
+ <w:insideH w:val=""single""
+ w:sz=""4""
+ w:space=""0""
+ w:color=""auto""/>
+ <w:insideV w:val=""single""
+ w:sz=""4""
+ w:space=""0""
+ w:color=""auto""/>
+ </w:tblBorders>
+ </w:tblPr>
+</w:style>";
+ var tgsElement = XElement.Parse(tableGridForRevisionsStyleMarkup);
+ sXDoc.Root.Add(tgsElement);
+ }
+ var tableNormalStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "TableNormal");
+ if (tableNormalStyle == null)
+ {
+ var tableNormalStyleMarkup =
+@"<w:style w:type=""table""
+ w:default=""1""
+ w:styleId=""TableNormal""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""Normal Table""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:tblPr>
+ <w:tblInd w:w=""0""
+ w:type=""dxa""/>
+ <w:tblCellMar>
+ <w:top w:w=""0""
+ w:type=""dxa""/>
+ <w:left w:w=""108""
+ w:type=""dxa""/>
+ <w:bottom w:w=""0""
+ w:type=""dxa""/>
+ <w:right w:w=""108""
+ w:type=""dxa""/>
+ </w:tblCellMar>
+ </w:tblPr>
+ </w:style>";
+ var tnsElement = XElement.Parse(tableNormalStyleMarkup);
+ sXDoc.Root.Add(tnsElement);
+ }
+ styleDefinitionsPart.PutXDocument();
+ }
+
+ private static XAttribute[] NamespaceAttributes =
+ {
+ new XAttribute(XNamespace.Xmlns + "wpc", WPC.wpc),
+ new XAttribute(XNamespace.Xmlns + "mc", MC.mc),
+ new XAttribute(XNamespace.Xmlns + "o", O.o),
+ new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XAttribute(XNamespace.Xmlns + "m", M.m),
+ new XAttribute(XNamespace.Xmlns + "v", VML.vml),
+ new XAttribute(XNamespace.Xmlns + "wp14", WP14.wp14),
+ new XAttribute(XNamespace.Xmlns + "wp", WP.wp),
+ new XAttribute(XNamespace.Xmlns + "w10", W10.w10),
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(XNamespace.Xmlns + "w14", W14.w14),
+ new XAttribute(XNamespace.Xmlns + "wpg", WPG.wpg),
+ new XAttribute(XNamespace.Xmlns + "wpi", WPI.wpi),
+ new XAttribute(XNamespace.Xmlns + "wne", WNE.wne),
+ new XAttribute(XNamespace.Xmlns + "wps", WPS.wps),
+ new XAttribute(MC.Ignorable, "w14 wp14"),
+ };
+
+ private static void AddFootnotesEndnotesParts(WordprocessingDocument wDoc)
+ {
+ var mdp = wDoc.MainDocumentPart;
+ if (mdp.FootnotesPart == null)
+ {
+ mdp.AddNewPart<FootnotesPart>();
+ var newFootnotes = wDoc.MainDocumentPart.FootnotesPart.GetXDocument();
+ newFootnotes.Declaration.Standalone = "yes";
+ newFootnotes.Declaration.Encoding = "UTF-8";
+ newFootnotes.Add(new XElement(W.footnotes, NamespaceAttributes));
+ mdp.FootnotesPart.PutXDocument();
+ }
+ if (mdp.EndnotesPart == null)
+ {
+ mdp.AddNewPart<EndnotesPart>();
+ var newEndnotes = wDoc.MainDocumentPart.EndnotesPart.GetXDocument();
+ newEndnotes.Declaration.Standalone = "yes";
+ newEndnotes.Declaration.Encoding = "UTF-8";
+ newEndnotes.Add(new XElement(W.endnotes, NamespaceAttributes));
+ mdp.EndnotesPart.PutXDocument();
+ }
+ }
+
+ private static void ChangeFootnoteEndnoteReferencesToUniqueRange(WordprocessingDocument wDoc, int startingIdForFootnotesEndnotes)
+ {
+ var mainDocPart = wDoc.MainDocumentPart;
+ var footnotesPart = wDoc.MainDocumentPart.FootnotesPart;
+ var endnotesPart = wDoc.MainDocumentPart.EndnotesPart;
+
+ var mainDocumentXDoc = mainDocPart.GetXDocument();
+ XDocument footnotesPartXDoc = null;
+ if (footnotesPart != null)
+ footnotesPartXDoc = footnotesPart.GetXDocument();
+ XDocument endnotesPartXDoc = null;
+ if (endnotesPart != null)
+ endnotesPartXDoc = endnotesPart.GetXDocument();
+
+ var references = mainDocumentXDoc
+ .Root
+ .Descendants()
+ .Where(d => d.Name == W.footnoteReference || d.Name == W.endnoteReference);
+
+ var rnd = new Random();
+ foreach (var r in references)
+ {
+ var oldId = (string)r.Attribute(W.id);
+ var newId = startingIdForFootnotesEndnotes.ToString();
+ startingIdForFootnotesEndnotes++;
+ r.Attribute(W.id).Value = newId;
+ if (r.Name == W.footnoteReference)
+ {
+ var fn = footnotesPartXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(e => (string)e.Attribute(W.id) == oldId);
+ if (fn == null)
+ throw new OpenXmlPowerToolsException("Invalid document");
+ fn.Attribute(W.id).Value = newId;
+ }
+ else
+ {
+ var en = endnotesPartXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(e => (string)e.Attribute(W.id) == oldId);
+ if (en == null)
+ throw new OpenXmlPowerToolsException("Invalid document");
+ en.Attribute(W.id).Value = newId;
+ }
+ }
+
+ mainDocPart.PutXDocument();
+ if (footnotesPart != null)
+ footnotesPart.PutXDocument();
+ if (endnotesPart != null)
+ endnotesPart.PutXDocument();
+ }
+
+ private static WmlDocument ProduceDocumentWithTrackedRevisions(WmlComparerSettings settings, WmlDocument wmlResult, WordprocessingDocument wDoc1, WordprocessingDocument wDoc2)
+ {
+ // save away sectPr so that can set in the newly produced document.
+ var savedSectPr = wDoc1
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Element(W.sectPr);
+
+ var contentParent1 = wDoc1.MainDocumentPart.GetXDocument().Root.Element(W.body);
+ AddSha1HashToBlockLevelContent(wDoc1.MainDocumentPart, contentParent1, settings);
+ var contentParent2 = wDoc2.MainDocumentPart.GetXDocument().Root.Element(W.body);
+ AddSha1HashToBlockLevelContent(wDoc2.MainDocumentPart, contentParent2, settings);
+
+ var cal1 = WmlComparer.CreateComparisonUnitAtomList(wDoc1.MainDocumentPart, wDoc1.MainDocumentPart.GetXDocument().Root.Element(W.body), settings);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in cal1)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var cus1 = GetComparisonUnitList(cal1, settings);
+
+ if (s_False)
+ {
+ var sbs = ComparisonUnit.ComparisonUnitListToString(cus1);
+ TestUtil.NotePad(sbs);
+ }
+
+ var cal2 = WmlComparer.CreateComparisonUnitAtomList(wDoc2.MainDocumentPart, wDoc2.MainDocumentPart.GetXDocument().Root.Element(W.body), settings);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in cal2)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var cus2 = GetComparisonUnitList(cal2, settings);
+
+ if (s_False)
+ {
+ var sbs = ComparisonUnit.ComparisonUnitListToString(cus2);
+ TestUtil.NotePad(sbs);
+ }
+
+ if (s_False)
+ {
+ var sb3 = new StringBuilder();
+ sb3.Append("ComparisonUnitList 1 =====" + Environment.NewLine + Environment.NewLine);
+ sb3.Append(ComparisonUnit.ComparisonUnitListToString(cus1));
+ sb3.Append(Environment.NewLine);
+ sb3.Append("ComparisonUnitList 2 =====" + Environment.NewLine + Environment.NewLine);
+ sb3.Append(ComparisonUnit.ComparisonUnitListToString(cus2));
+ var sbs3 = sb3.ToString();
+ TestUtil.NotePad(sbs3);
+ }
+
+ var correlatedSequence = Lcs(cus1, cus2, settings);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in correlatedSequence)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // for any deleted or inserted rows, we go into the w:trPr properties, and add the appropriate w:ins or w:del element, and therefore
+ // when generating the document, the appropriate row will be marked as deleted or inserted.
+ MarkRowsAsDeletedOrInserted(settings, correlatedSequence);
+
+ // the following gets a flattened list of ComparisonUnitAtoms, with status indicated in each ComparisonUnitAtom: Deleted, Inserted, or Equal
+ var listOfComparisonUnitAtoms = FlattenToComparisonUnitAtomList(correlatedSequence, settings);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in listOfComparisonUnitAtoms)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // note - we don't want to do the hack until after flattening all of the groups. At the end of the flattening, we should simply
+ // have a list of ComparisonUnitAtoms, appropriately marked as equal, inserted, or deleted.
+
+ // the table id will be hacked in the normal course of events.
+ // in the case where a row is deleted, not necessary to hack - the deleted row ID will do.
+ // in the case where a row is inserted, not necessary to hack - the inserted row ID will do as well.
+ AssembleAncestorUnidsInOrderToRebuildXmlTreeProperly(listOfComparisonUnitAtoms);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in listOfComparisonUnitAtoms)
+ sb.Append(item.ToStringAncestorUnids() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // and then finally can generate the document with revisions
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(wmlResult.DocumentByteArray, 0, wmlResult.DocumentByteArray.Length);
+ using (WordprocessingDocument wDocWithRevisions = WordprocessingDocument.Open(ms, true))
+ {
+ var xDoc = wDocWithRevisions.MainDocumentPart.GetXDocument();
+ var rootNamespaceAttributes = xDoc
+ .Root
+ .Attributes()
+ .Where(a => a.IsNamespaceDeclaration || a.Name.Namespace == MC.mc)
+ .ToList();
+
+ // ======================================
+ // The following produces a new valid WordprocessingML document from the listOfComparisonUnitAtoms
+ var newBodyChildren = ProduceNewWmlMarkupFromCorrelatedSequence(wDocWithRevisions.MainDocumentPart,
+ listOfComparisonUnitAtoms, settings);
+
+ XDocument newXDoc = new XDocument();
+ newXDoc.Add(
+ new XElement(W.document,
+ rootNamespaceAttributes,
+ new XElement(W.body, newBodyChildren)));
+ MarkContentAsDeletedOrInserted(newXDoc, settings);
+ CoalesceAdjacentRunsWithIdenticalFormatting(newXDoc);
+ IgnorePt14Namespace(newXDoc.Root);
+
+ ProcessFootnoteEndnote(settings,
+ listOfComparisonUnitAtoms,
+ wDoc1.MainDocumentPart,
+ wDoc2.MainDocumentPart,
+ newXDoc);
+
+ RectifyFootnoteEndnoteIds(
+ wDoc1.MainDocumentPart,
+ wDoc2.MainDocumentPart,
+ wDocWithRevisions.MainDocumentPart,
+ newXDoc,
+ settings);
+
+ ConjoinDeletedInsertedParagraphMarks(wDocWithRevisions.MainDocumentPart, newXDoc);
+
+ FixUpRevisionIds(wDocWithRevisions, newXDoc);
+
+ // little bit of cleanup
+ MoveLastSectPrToChildOfBody(newXDoc);
+ XElement newXDoc2Root = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(newXDoc.Root);
+ xDoc.Root.ReplaceWith(newXDoc2Root);
+
+ /**********************************************************************************************/
+ // temporary code to remove sections. When remove this code, get validation errors for some ITU documents.
+ xDoc.Root.Descendants(W.sectPr).Remove();
+
+ // move w:sectPr from source document into newly generated document.
+ if (savedSectPr != null)
+ {
+ var xd = wDocWithRevisions.MainDocumentPart.GetXDocument();
+ // add everything but headers/footers
+ var clonedSectPr = new XElement(W.sectPr,
+ savedSectPr.Attributes(),
+ savedSectPr.Element(W.type),
+ savedSectPr.Element(W.pgSz),
+ savedSectPr.Element(W.pgMar),
+ savedSectPr.Element(W.cols),
+ savedSectPr.Element(W.titlePg));
+ xd.Root.Element(W.body).Add(clonedSectPr);
+ }
+ /**********************************************************************************************/
+
+ wDocWithRevisions.MainDocumentPart.PutXDocument();
+ FixUpFootnotesEndnotesWithCustomMarkers(wDocWithRevisions);
+ FixUpRevMarkIds(wDocWithRevisions);
+ FixUpDocPrIds(wDocWithRevisions);
+ FixUpShapeIds(wDocWithRevisions);
+ FixUpShapeTypeIds(wDocWithRevisions);
+ AddFootnotesEndnotesStyles(wDocWithRevisions);
+ CopyMissingStylesFromOneDocToAnother(wDoc2, wDocWithRevisions);
+ DeleteFootnotePropertiesInSettings(wDocWithRevisions);
+ }
+ foreach (var part in wDoc1.ContentParts())
+ part.PutXDocument();
+ foreach (var part in wDoc2.ContentParts())
+ part.PutXDocument();
+ var updatedWmlResult = new WmlDocument("Dummy.docx", ms.ToArray());
+ return updatedWmlResult;
+ }
+ }
+
+ private static void DeleteFootnotePropertiesInSettings(WordprocessingDocument wDocWithRevisions)
+ {
+ var settingsPart = wDocWithRevisions.MainDocumentPart.DocumentSettingsPart;
+ if (settingsPart != null)
+ {
+ var sxDoc = settingsPart.GetXDocument();
+ sxDoc.Root.Elements().Where(e => e.Name == W.footnotePr || e.Name == W.endnotePr).Remove();
+ settingsPart.PutXDocument();
+ }
+ }
+
+ private static void FixUpFootnotesEndnotesWithCustomMarkers(WordprocessingDocument wDocWithRevisions)
+ {
+#if FALSE
+ // this needs to change
+ <w:del w:author="Open-Xml-PowerTools"
+ w:id="7"
+ w:date="2017-06-07T12:23:22.8601285-07:00">
+ <w:r>
+ <w:rPr pt14:Unid="ec75a71361c84562a757eee8b28fc229">
+ <w:rFonts w:cs="Times New Roman Bold"
+ pt14:Unid="16bb355df5964ba09854f9152c97242b" />
+ <w:b w:val="0"
+ pt14:Unid="9abcec54ad414791a5627cbb198e8aa9" />
+ <w:bCs pt14:Unid="71ecd2eba85e4bfaa92b3d618e2f8829" />
+ <w:position w:val="6"
+ pt14:Unid="61793f6a5f494700b7f2a3a753ce9055" />
+ <w:sz w:val="16"
+ pt14:Unid="60b3cd020c214d0ea07e5a68ae0e4efe" />
+ <w:szCs w:val="16"
+ pt14:Unid="9ae61a724de44a75868180aac44ea380" />
+ </w:rPr>
+ <w:footnoteReference w:customMarkFollows="1"
+ w:id="1"
+ pt14:Status="Deleted" />
+ </w:r>
+ </w:del>
+ <w:del w:author="Open-Xml-PowerTools"
+ w:id="8"
+ w:date="2017-06-07T12:23:22.8601285-07:00">
+ <w:r>
+ <w:rPr pt14:Unid="445caef74a624e588e7adaa6d7775639">
+ <w:rFonts w:cs="Times New Roman Bold"
+ pt14:Unid="5920885f8ec44c53bcaece2de7eafda2" />
+ <w:b w:val="0"
+ pt14:Unid="023a29e2e6d44c3b8c5df47317ace4c6" />
+ <w:bCs pt14:Unid="e96e37daf9174b268ef4731df831df7d" />
+ <w:position w:val="6"
+ pt14:Unid="be3f8ff7ed0745ae9340bb2706b28b1f" />
+ <w:sz w:val="16"
+ pt14:Unid="6fbbde024e7c46b9b72435ae50065459" />
+ <w:szCs w:val="16"
+ pt14:Unid="cc82e7bd75f441f2b609eae0672fb285" />
+ </w:rPr>
+ <w:delText>1</w:delText>
+ </w:r>
+ </w:del>
+
+ // to this
+ <w:del w:author="Open-Xml-PowerTools"
+ w:id="7"
+ w:date="2017-06-07T12:23:22.8601285-07:00">
+ <w:r>
+ <w:rPr pt14:Unid="ec75a71361c84562a757eee8b28fc229">
+ <w:rFonts w:cs="Times New Roman Bold"
+ pt14:Unid="16bb355df5964ba09854f9152c97242b" />
+ <w:b w:val="0"
+ pt14:Unid="9abcec54ad414791a5627cbb198e8aa9" />
+ <w:bCs pt14:Unid="71ecd2eba85e4bfaa92b3d618e2f8829" />
+ <w:position w:val="6"
+ pt14:Unid="61793f6a5f494700b7f2a3a753ce9055" />
+ <w:sz w:val="16"
+ pt14:Unid="60b3cd020c214d0ea07e5a68ae0e4efe" />
+ <w:szCs w:val="16"
+ pt14:Unid="9ae61a724de44a75868180aac44ea380" />
+ </w:rPr>
+ <w:footnoteReference w:customMarkFollows="1"
+ w:id="1"
+ pt14:Status="Deleted" />
+ <w:delText>1</w:delText>
+ </w:r>
+ </w:del>
+#endif
+ // this is pretty random - a bug in Word prevents display of a document if the delText element does not immediately follow the footnoteReference element, in the same run.
+ var mainXDoc = wDocWithRevisions.MainDocumentPart.GetXDocument();
+ var newRoot = (XElement)FootnoteEndnoteReferenceCleanupTransform(mainXDoc.Root);
+ mainXDoc.Root.ReplaceWith(newRoot);
+ wDocWithRevisions.MainDocumentPart.PutXDocument();
+ }
+
+ private static object FootnoteEndnoteReferenceCleanupTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ // small optimization to eliminate the work for most elements
+ if (element.Element(W.del) != null || element.Element(W.ins) != null)
+ {
+ var hasFootnoteEndnoteReferencesThatNeedCleanedUp = element
+ .Elements()
+ .Where(e => e.Name == W.del || e.Name == W.ins)
+ .Elements(W.r)
+ .Elements()
+ .Where(e => e.Name == W.footnoteReference || e.Name == W.endnoteReference)
+ .Attributes(W.customMarkFollows)
+ .Any();
+
+ if (hasFootnoteEndnoteReferencesThatNeedCleanedUp)
+ {
+ var clone = new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => FootnoteEndnoteReferenceCleanupTransform(n)));
+ var footnoteEndnoteReferencesToAdjust = clone
+ .Descendants()
+ .Where(d => d.Name == W.footnoteReference || d.Name == W.endnoteReference)
+ .Where(d => d.Attribute(W.customMarkFollows) != null);
+ foreach (var fnenr in footnoteEndnoteReferencesToAdjust)
+ {
+ var par = fnenr.Parent;
+ var gp = fnenr.Parent.Parent;
+ if (par.Name == W.r &&
+ gp.Name == W.del)
+ {
+ if (par.Element(W.delText) != null)
+ continue;
+ var afterGp = gp.ElementsAfterSelf().FirstOrDefault();
+ if (afterGp == null)
+ continue;
+ var afterGpDelText = afterGp.Elements(W.r).Elements(W.delText);
+ if (afterGpDelText.Any())
+ {
+ par.Add(afterGpDelText); // this will clone and add to run that contains the reference
+ afterGpDelText.Remove(); // this leaves an empty run, does not matter.
+ }
+ }
+ if (par.Name == W.r &&
+ gp.Name == W.ins)
+ {
+ if (par.Element(W.t) != null)
+ continue;
+ var afterGp = gp.ElementsAfterSelf().FirstOrDefault();
+ if (afterGp == null)
+ continue;
+ var afterGpText = afterGp.Elements(W.r).Elements(W.t);
+ if (afterGpText.Any())
+ {
+ par.Add(afterGpText); // this will clone and add to run that contains the reference
+ afterGpText.Remove(); // this leaves an empty run, does not matter.
+ }
+ }
+ }
+ return clone;
+ }
+ }
+ else
+ {
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => FootnoteEndnoteReferenceCleanupTransform(n)));
+ }
+ }
+ return node;
+ }
+
+ private static void CopyMissingStylesFromOneDocToAnother(WordprocessingDocument wDocFrom, WordprocessingDocument wDocTo)
+ {
+ var revisionsStylesXDoc = wDocTo.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ var afterStylesXDoc = wDocFrom.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ foreach (var style in afterStylesXDoc.Root.Elements(W.style))
+ {
+ var type = (string)style.Attribute(W.type);
+ var styleId = (string)style.Attribute(W.styleId);
+ var styleInRevDoc = revisionsStylesXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => (string)st.Attribute(W.type) == type &&
+ (string)st.Attribute(W.styleId) == styleId);
+ if (styleInRevDoc != null)
+ continue;
+ var cloned = new XElement(style);
+ if (cloned.Attribute(W._default) != null)
+ cloned.Attribute(W._default).Remove();
+ revisionsStylesXDoc.Root.Add(cloned);
+ }
+ wDocTo.MainDocumentPart.StyleDefinitionsPart.PutXDocument();
+ }
+
+ private static void AddFootnotesEndnotesStyles(WordprocessingDocument wDocWithRevisions)
+ {
+ var mainXDoc = wDocWithRevisions.MainDocumentPart.GetXDocument();
+ var hasFootnotes = mainXDoc.Descendants(W.footnoteReference).Any();
+ var hasEndnotes = mainXDoc.Descendants(W.endnoteReference).Any();
+ var styleDefinitionsPart = wDocWithRevisions.MainDocumentPart.StyleDefinitionsPart;
+ var sXDoc = styleDefinitionsPart.GetXDocument();
+ if (hasFootnotes)
+ {
+ var footnoteTextStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "FootnoteText");
+ if (footnoteTextStyle == null)
+ {
+ var footnoteTextStyleMarkup =
+@"<w:style w:type=""paragraph""
+ w:styleId=""FootnoteText""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""footnote text""/>
+ <w:basedOn w:val=""Normal""/>
+ <w:link w:val=""FootnoteTextChar""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after=""0""
+ w:line=""240""
+ w:lineRule=""auto""/>
+ </w:pPr>
+ <w:rPr>
+ <w:sz w:val=""20""/>
+ <w:szCs w:val=""20""/>
+ </w:rPr>
+ </w:style>";
+ var ftsElement = XElement.Parse(footnoteTextStyleMarkup);
+ sXDoc.Root.Add(ftsElement);
+ }
+ var footnoteTextCharStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "FootnoteTextChar");
+ if (footnoteTextCharStyle == null)
+ {
+ var footnoteTextCharStyleMarkup =
+@"<w:style w:type=""character""
+ w:customStyle=""1""
+ w:styleId=""FootnoteTextChar""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""Footnote Text Char""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:link w:val=""FootnoteText""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:sz w:val=""20""/>
+ <w:szCs w:val=""20""/>
+ </w:rPr>
+ </w:style>";
+ var fntcsElement = XElement.Parse(footnoteTextCharStyleMarkup);
+ sXDoc.Root.Add(fntcsElement);
+ }
+ var footnoteReferenceStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "FootnoteReference");
+ if (footnoteReferenceStyle == null)
+ {
+ var footnoteReferenceStyleMarkup =
+@"<w:style w:type=""character""
+ w:styleId=""FootnoteReference""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""footnote reference""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:rPr>
+ <w:vertAlign w:val=""superscript""/>
+ </w:rPr>
+ </w:style>";
+ var fnrsElement = XElement.Parse(footnoteReferenceStyleMarkup);
+ sXDoc.Root.Add(fnrsElement);
+ }
+ }
+ if (hasEndnotes)
+ {
+ var endnoteTextStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "EndnoteText");
+ if (endnoteTextStyle == null)
+ {
+ var endnoteTextStyleMarkup =
+@"<w:style w:type=""paragraph""
+ w:styleId=""EndnoteText""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""endnote text""/>
+ <w:basedOn w:val=""Normal""/>
+ <w:link w:val=""EndnoteTextChar""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:pPr>
+ <w:spacing w:after=""0""
+ w:line=""240""
+ w:lineRule=""auto""/>
+ </w:pPr>
+ <w:rPr>
+ <w:sz w:val=""20""/>
+ <w:szCs w:val=""20""/>
+ </w:rPr>
+ </w:style>";
+ var etsElement = XElement.Parse(endnoteTextStyleMarkup);
+ sXDoc.Root.Add(etsElement);
+ }
+ var endnoteTextCharStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "EndnoteTextChar");
+ if (endnoteTextCharStyle == null)
+ {
+ var endnoteTextCharStyleMarkup =
+@"<w:style w:type=""character""
+ w:customStyle=""1""
+ w:styleId=""EndnoteTextChar""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""Endnote Text Char""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:link w:val=""EndnoteText""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:sz w:val=""20""/>
+ <w:szCs w:val=""20""/>
+ </w:rPr>
+ </w:style>";
+ var entcsElement = XElement.Parse(endnoteTextCharStyleMarkup);
+ sXDoc.Root.Add(entcsElement);
+ }
+ var endnoteReferenceStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W.styleId) == "EndnoteReference");
+ if (endnoteReferenceStyle == null)
+ {
+ var endnoteReferenceStyleMarkup =
+@"<w:style w:type=""character""
+ w:styleId=""EndnoteReference""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""endnote reference""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:rPr>
+ <w:vertAlign w:val=""superscript""/>
+ </w:rPr>
+ </w:style>";
+ var enrsElement = XElement.Parse(endnoteReferenceStyleMarkup);
+ sXDoc.Root.Add(enrsElement);
+ }
+ }
+ if (hasFootnotes || hasEndnotes)
+ {
+ styleDefinitionsPart.PutXDocument();
+ }
+ }
+
+ // it is possible, per the algorithm, for the algorithm to find that the paragraph mark for a single paragraph has been
+ // inserted and deleted. If the algorithm sets them to equal, then sometimes it will equate paragraph marks that should
+ // not be equated.
+ private static void ConjoinDeletedInsertedParagraphMarks(MainDocumentPart mainDocumentPart, XDocument newXDoc)
+ {
+ ConjoinMultipleParagraphMarks(newXDoc);
+ if (mainDocumentPart.FootnotesPart != null)
+ {
+ var fnXDoc = mainDocumentPart.FootnotesPart.GetXDocument();
+ ConjoinMultipleParagraphMarks(fnXDoc);
+ mainDocumentPart.FootnotesPart.PutXDocument();
+ }
+ if (mainDocumentPart.EndnotesPart != null)
+ {
+ var fnXDoc = mainDocumentPart.EndnotesPart.GetXDocument();
+ ConjoinMultipleParagraphMarks(fnXDoc);
+ mainDocumentPart.EndnotesPart.PutXDocument();
+ }
+ }
+
+ private static void ConjoinMultipleParagraphMarks(XDocument xDoc)
+ {
+ var newRoot = ConjoinTransform(xDoc.Root);
+ xDoc.Root.ReplaceWith(newRoot);
+ }
+
+ private static object ConjoinTransform(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p && element.Elements(W.pPr).Count() >= 2)
+ {
+ var pPr = new XElement(element.Element(W.pPr));
+ pPr.Elements(W.rPr).Elements().Where(r => r.Name == W.ins || r.Name == W.del).Remove();
+ pPr.Attributes(PtOpenXml.Status).Remove();
+ var newPara = new XElement(W.p,
+ element.Attributes(),
+ pPr,
+ element.Elements().Where(c => c.Name != W.pPr));
+ return newPara;
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => ConjoinTransform(n)));
+ }
+ return node;
+ }
+
+ private static void MarkContentAsDeletedOrInserted(XDocument newXDoc, WmlComparerSettings settings)
+ {
+ var newRoot = MarkContentAsDeletedOrInsertedTransform(newXDoc.Root, settings);
+ newXDoc.Root.ReplaceWith(newRoot);
+ }
+
+ private static object MarkContentAsDeletedOrInsertedTransform(XNode node, WmlComparerSettings settings)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.r)
+ {
+ var statusList = element
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(d => d.Name == W.t || d.Name == W.delText || AllowableRunChildren.Contains(d.Name))
+ .Attributes(PtOpenXml.Status)
+ .Select(a => (string)a)
+ .Distinct()
+ .ToList();
+ if (statusList.Count() > 1)
+ throw new OpenXmlPowerToolsException("Internal error - have both deleted and inserted text elements in the same run.");
+ if (statusList.Count() == 0)
+ return new XElement(W.r,
+ element.Attributes(),
+ element.Nodes().Select(n => MarkContentAsDeletedOrInsertedTransform(n, settings)));
+ if (statusList.First() == "Deleted")
+ {
+ return new XElement(W.del,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions),
+ new XElement(W.r,
+ element.Attributes(),
+ element.Nodes().Select(n => MarkContentAsDeletedOrInsertedTransform(n, settings))));
+ }
+ else if (statusList.First() == "Inserted")
+ {
+ return new XElement(W.ins,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions),
+ new XElement(W.r,
+ element.Attributes(),
+ element.Nodes().Select(n => MarkContentAsDeletedOrInsertedTransform(n, settings))));
+ }
+ }
+
+ if (element.Name == W.pPr)
+ {
+ var status = (string)element.Attribute(PtOpenXml.Status);
+ if (status == null)
+ return new XElement(W.pPr,
+ element.Attributes(),
+ element.Nodes().Select(n => MarkContentAsDeletedOrInsertedTransform(n, settings)));
+ var pPr = new XElement(element);
+ if (status == "Deleted")
+ {
+ XElement rPr = pPr.Element(W.rPr);
+ if (rPr == null)
+ rPr = new XElement(W.rPr);
+ rPr.Add(new XElement(W.del,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions)));
+ if (pPr.Element(W.rPr) != null)
+ pPr.Element(W.rPr).ReplaceWith(rPr);
+ else
+ pPr.AddFirst(rPr);
+ }
+ else if (status == "Inserted")
+ {
+ XElement rPr = pPr.Element(W.rPr);
+ if (rPr == null)
+ rPr = new XElement(W.rPr);
+ rPr.Add(new XElement(W.ins,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions)));
+ if (pPr.Element(W.rPr) != null)
+ pPr.Element(W.rPr).ReplaceWith(rPr);
+ else
+ pPr.AddFirst(rPr);
+ }
+ else
+ throw new OpenXmlPowerToolsException("Internal error");
+ return pPr;
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => MarkContentAsDeletedOrInsertedTransform(n, settings)));
+ }
+ return node;
+ }
+
+ private static void FixUpRevisionIds(WordprocessingDocument wDocWithRevisions, XDocument newXDoc)
+ {
+ IEnumerable<XElement> footnoteRevisions = Enumerable.Empty<XElement>();
+ if (wDocWithRevisions.MainDocumentPart.FootnotesPart != null)
+ {
+ var fnxd = wDocWithRevisions.MainDocumentPart.FootnotesPart.GetXDocument();
+ footnoteRevisions = fnxd
+ .Descendants()
+ .Where(d => d.Name == W.ins || d.Name == W.del);
+ }
+ IEnumerable<XElement> endnoteRevisions = Enumerable.Empty<XElement>();
+ if (wDocWithRevisions.MainDocumentPart.EndnotesPart != null)
+ {
+ var fnxd = wDocWithRevisions.MainDocumentPart.EndnotesPart.GetXDocument();
+ endnoteRevisions = fnxd
+ .Descendants()
+ .Where(d => d.Name == W.ins || d.Name == W.del);
+ }
+ var mainRevisions = newXDoc
+ .Descendants()
+ .Where(d => d.Name == W.ins || d.Name == W.del);
+ var allRevisions = mainRevisions
+ .Concat(footnoteRevisions)
+ .Concat(endnoteRevisions)
+ .Select((r, i) =>
+ {
+ return new
+ {
+ Rev = r,
+ Idx = i + 1,
+ };
+ });
+ foreach (var item in allRevisions)
+ item.Rev.Attribute(W.id).Value = item.Idx.ToString();
+ if (wDocWithRevisions.MainDocumentPart.FootnotesPart != null)
+ wDocWithRevisions.MainDocumentPart.FootnotesPart.PutXDocument();
+ if (wDocWithRevisions.MainDocumentPart.EndnotesPart != null)
+ wDocWithRevisions.MainDocumentPart.EndnotesPart.PutXDocument();
+ }
+
+ private static void IgnorePt14Namespace(XElement root)
+ {
+ if (root.Attribute(XNamespace.Xmlns + "pt14") == null)
+ {
+ root.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName));
+ }
+ var ignorable = (string)root.Attribute(MC.Ignorable);
+ if (ignorable != null)
+ {
+ var list = ignorable.Split(' ');
+ if (!list.Contains("pt14"))
+ {
+ ignorable += " pt14";
+ root.Attribute(MC.Ignorable).Value = ignorable;
+ }
+ }
+ else
+ {
+ root.Add(new XAttribute(MC.Ignorable, "pt14"));
+ }
+ }
+
+ private static void CoalesceAdjacentRunsWithIdenticalFormatting(XDocument xDoc)
+ {
+ var paras = xDoc.Root.DescendantsTrimmed(W.txbxContent).Where(d => d.Name == W.p);
+ foreach (var para in paras)
+ {
+ var newPara = WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(para);
+ para.ReplaceNodes(newPara.Nodes());
+ }
+ }
+
+ private static void ProcessFootnoteEndnote(
+ WmlComparerSettings settings,
+ List<ComparisonUnitAtom> listOfComparisonUnitAtoms,
+ MainDocumentPart mainDocumentPartBefore,
+ MainDocumentPart mainDocumentPartAfter,
+ XDocument mainDocumentXDoc)
+ {
+ var footnotesPartBefore = mainDocumentPartBefore.FootnotesPart;
+ var endnotesPartBefore = mainDocumentPartBefore.EndnotesPart;
+ var footnotesPartAfter = mainDocumentPartAfter.FootnotesPart;
+ var endnotesPartAfter = mainDocumentPartAfter.EndnotesPart;
+
+ XDocument footnotesPartBeforeXDoc = null;
+ if (footnotesPartBefore != null)
+ footnotesPartBeforeXDoc = footnotesPartBefore.GetXDocument();
+ XDocument footnotesPartAfterXDoc = null;
+ if (footnotesPartAfter != null)
+ footnotesPartAfterXDoc = footnotesPartAfter.GetXDocument();
+ XDocument endnotesPartBeforeXDoc = null;
+ if (endnotesPartBefore != null)
+ endnotesPartBeforeXDoc = endnotesPartBefore.GetXDocument();
+ XDocument endnotesPartAfterXDoc = null;
+ if (endnotesPartAfter != null)
+ endnotesPartAfterXDoc = endnotesPartAfter.GetXDocument();
+
+ var possiblyModifiedFootnotesEndNotes = listOfComparisonUnitAtoms
+ .Where(cua =>
+ cua.ContentElement.Name == W.footnoteReference ||
+ cua.ContentElement.Name == W.endnoteReference)
+ .ToList();
+
+ foreach (var fn in possiblyModifiedFootnotesEndNotes)
+ {
+ string beforeId = null;
+ if (fn.ContentElementBefore != null)
+ beforeId = (string)fn.ContentElementBefore.Attribute(W.id);
+ var afterId = (string)fn.ContentElement.Attribute(W.id);
+
+ XElement footnoteEndnoteBefore = null;
+ XElement footnoteEndnoteAfter = null;
+ OpenXmlPart partToUseBefore = null;
+ OpenXmlPart partToUseAfter = null;
+ XDocument partToUseBeforeXDoc = null;
+ XDocument partToUseAfterXDoc = null;
+
+ if (fn.CorrelationStatus == CorrelationStatus.Equal)
+ {
+ if (fn.ContentElement.Name == W.footnoteReference)
+ {
+ footnoteEndnoteBefore = footnotesPartBeforeXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == beforeId);
+ footnoteEndnoteAfter = footnotesPartAfterXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == afterId);
+ partToUseBefore = footnotesPartBefore;
+ partToUseAfter = footnotesPartAfter;
+ partToUseBeforeXDoc = footnotesPartBeforeXDoc;
+ partToUseAfterXDoc = footnotesPartAfterXDoc;
+ }
+ else
+ {
+ footnoteEndnoteBefore = endnotesPartBeforeXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == beforeId);
+ footnoteEndnoteAfter = endnotesPartAfterXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == afterId);
+ partToUseBefore = endnotesPartBefore;
+ partToUseAfter = endnotesPartAfter;
+ partToUseBeforeXDoc = endnotesPartBeforeXDoc;
+ partToUseAfterXDoc = endnotesPartAfterXDoc;
+ }
+ AddSha1HashToBlockLevelContent(partToUseBefore, footnoteEndnoteBefore, settings);
+ AddSha1HashToBlockLevelContent(partToUseAfter, footnoteEndnoteAfter, settings);
+
+ var fncal1 = WmlComparer.CreateComparisonUnitAtomList(partToUseBefore, footnoteEndnoteBefore, settings);
+ var fncus1 = GetComparisonUnitList(fncal1, settings);
+
+ var fncal2 = WmlComparer.CreateComparisonUnitAtomList(partToUseAfter, footnoteEndnoteAfter, settings);
+ var fncus2 = GetComparisonUnitList(fncal2, settings);
+
+ if (! (fncus1.Length == 0 && fncus2.Length == 0))
+ {
+ var fnCorrelatedSequence = Lcs(fncus1, fncus2, settings);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in fnCorrelatedSequence)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // for any deleted or inserted rows, we go into the w:trPr properties, and add the appropriate w:ins or w:del element, and therefore
+ // when generating the document, the appropriate row will be marked as deleted or inserted.
+ MarkRowsAsDeletedOrInserted(settings, fnCorrelatedSequence);
+
+ // the following gets a flattened list of ComparisonUnitAtoms, with status indicated in each ComparisonUnitAtom: Deleted, Inserted, or Equal
+ var fnListOfComparisonUnitAtoms = FlattenToComparisonUnitAtomList(fnCorrelatedSequence, settings);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in fnListOfComparisonUnitAtoms)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // hack = set the guid ID of the table, row, or cell from the 'before' document to be equal to the 'after' document.
+
+ // note - we don't want to do the hack until after flattening all of the groups. At the end of the flattening, we should simply
+ // have a list of ComparisonUnitAtoms, appropriately marked as equal, inserted, or deleted.
+
+ // the table id will be hacked in the normal course of events.
+ // in the case where a row is deleted, not necessary to hack - the deleted row ID will do.
+ // in the case where a row is inserted, not necessary to hack - the inserted row ID will do as well.
+ AssembleAncestorUnidsInOrderToRebuildXmlTreeProperly(fnListOfComparisonUnitAtoms);
+
+ var newFootnoteEndnoteChildren = ProduceNewWmlMarkupFromCorrelatedSequence(partToUseAfter, fnListOfComparisonUnitAtoms, settings);
+ var tempElement = new XElement(W.body, newFootnoteEndnoteChildren);
+ var hasFootnoteReference = tempElement.Descendants(W.r).Any(r =>
+ {
+ var b = false;
+ if ((string)r.Elements(W.rPr).Elements(W.rStyle).Attributes(W.val).FirstOrDefault() == "FootnoteReference")
+ b = true;
+ if (r.Descendants(W.footnoteRef).Any())
+ b = true;
+ return b;
+ });
+ if (!hasFootnoteReference)
+ {
+ var firstPara = tempElement.Descendants(W.p).FirstOrDefault();
+ if (firstPara != null)
+ {
+ var firstRun = firstPara.Element(W.r);
+ if (firstRun != null)
+ {
+ if (fn.ContentElement.Name == W.footnoteReference)
+ firstRun.AddBeforeSelf(
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "FootnoteReference"))),
+ new XElement(W.footnoteRef)));
+ else
+ firstRun.AddBeforeSelf(
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "EndnoteReference"))),
+ new XElement(W.endnoteRef)));
+ }
+ }
+ }
+ XElement newTempElement = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(tempElement);
+ var newContentElement = newTempElement.Descendants().FirstOrDefault(d => d.Name == W.footnote || d.Name == W.endnote);
+ if (newContentElement == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ footnoteEndnoteAfter.ReplaceNodes(newContentElement.Nodes());
+ }
+ }
+ else if (fn.CorrelationStatus == CorrelationStatus.Inserted)
+ {
+ if (fn.ContentElement.Name == W.footnoteReference)
+ {
+ footnoteEndnoteAfter = footnotesPartAfterXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == afterId);
+ partToUseAfter = footnotesPartAfter;
+ partToUseAfterXDoc = footnotesPartAfterXDoc;
+ }
+ else
+ {
+ footnoteEndnoteAfter = endnotesPartAfterXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == afterId);
+ partToUseAfter = endnotesPartAfter;
+ partToUseAfterXDoc = endnotesPartAfterXDoc;
+ }
+
+ AddSha1HashToBlockLevelContent(partToUseAfter, footnoteEndnoteAfter, settings);
+
+ var fncal2 = WmlComparer.CreateComparisonUnitAtomList(partToUseAfter, footnoteEndnoteAfter, settings);
+ var fncus2 = GetComparisonUnitList(fncal2, settings);
+
+ var insertedCorrSequ = new List<CorrelatedSequence>() {
+ new CorrelatedSequence()
+ {
+ ComparisonUnitArray1 = null,
+ ComparisonUnitArray2 = fncus2,
+ CorrelationStatus = CorrelationStatus.Inserted,
+ },
+ };
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in insertedCorrSequ)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ MarkRowsAsDeletedOrInserted(settings, insertedCorrSequ);
+
+ var fnListOfComparisonUnitAtoms = FlattenToComparisonUnitAtomList(insertedCorrSequ, settings);
+
+ AssembleAncestorUnidsInOrderToRebuildXmlTreeProperly(fnListOfComparisonUnitAtoms);
+
+ var newFootnoteEndnoteChildren = ProduceNewWmlMarkupFromCorrelatedSequence(partToUseAfter,
+ fnListOfComparisonUnitAtoms, settings);
+ var tempElement = new XElement(W.body, newFootnoteEndnoteChildren);
+ var hasFootnoteReference = tempElement.Descendants(W.r).Any(r =>
+ {
+ var b = false;
+ if ((string)r.Elements(W.rPr).Elements(W.rStyle).Attributes(W.val).FirstOrDefault() == "FootnoteReference")
+ b = true;
+ if (r.Descendants(W.footnoteRef).Any())
+ b = true;
+ return b;
+ });
+ if (!hasFootnoteReference)
+ {
+ var firstPara = tempElement.Descendants(W.p).FirstOrDefault();
+ if (firstPara != null)
+ {
+ var firstRun = firstPara.Descendants(W.r).FirstOrDefault();
+ if (firstRun != null)
+ {
+ if (fn.ContentElement.Name == W.footnoteReference)
+ firstRun.AddBeforeSelf(
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "FootnoteReference"))),
+ new XElement(W.footnoteRef)));
+ else
+ firstRun.AddBeforeSelf(
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "EndnoteReference"))),
+ new XElement(W.endnoteRef)));
+ }
+ }
+ }
+ XElement newTempElement = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(tempElement);
+ var newContentElement = newTempElement
+ .Descendants()
+ .FirstOrDefault(d => d.Name == W.footnote || d.Name == W.endnote);
+ if (newContentElement == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ footnoteEndnoteAfter.ReplaceNodes(newContentElement.Nodes());
+ }
+ else if (fn.CorrelationStatus == CorrelationStatus.Deleted)
+ {
+ if (fn.ContentElement.Name == W.footnoteReference)
+ {
+ footnoteEndnoteBefore = footnotesPartBeforeXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == afterId);
+ partToUseAfter = footnotesPartAfter;
+ partToUseAfterXDoc = footnotesPartAfterXDoc;
+ }
+ else
+ {
+ footnoteEndnoteBefore = endnotesPartBeforeXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(fnn => (string)fnn.Attribute(W.id) == afterId);
+ partToUseBefore = endnotesPartBefore;
+ partToUseBeforeXDoc = endnotesPartBeforeXDoc;
+ }
+
+ AddSha1HashToBlockLevelContent(partToUseBefore, footnoteEndnoteBefore, settings);
+
+ var fncal2 = WmlComparer.CreateComparisonUnitAtomList(partToUseBefore, footnoteEndnoteBefore, settings);
+ var fncus2 = GetComparisonUnitList(fncal2, settings);
+
+ var deletedCorrSequ = new List<CorrelatedSequence>() {
+ new CorrelatedSequence()
+ {
+ ComparisonUnitArray1 = fncus2,
+ ComparisonUnitArray2 = null,
+ CorrelationStatus = CorrelationStatus.Deleted,
+ },
+ };
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in deletedCorrSequ)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ MarkRowsAsDeletedOrInserted(settings, deletedCorrSequ);
+
+ var fnListOfComparisonUnitAtoms = FlattenToComparisonUnitAtomList(deletedCorrSequ, settings);
+
+ if (fnListOfComparisonUnitAtoms.Any())
+ {
+ AssembleAncestorUnidsInOrderToRebuildXmlTreeProperly(fnListOfComparisonUnitAtoms);
+
+ var newFootnoteEndnoteChildren = ProduceNewWmlMarkupFromCorrelatedSequence(partToUseBefore,
+ fnListOfComparisonUnitAtoms, settings);
+ var tempElement = new XElement(W.body, newFootnoteEndnoteChildren);
+ var hasFootnoteReference = tempElement.Descendants(W.r).Any(r =>
+ {
+ var b = false;
+ if ((string)r.Elements(W.rPr).Elements(W.rStyle).Attributes(W.val).FirstOrDefault() == "FootnoteReference")
+ b = true;
+ if (r.Descendants(W.footnoteRef).Any())
+ b = true;
+ return b;
+ });
+ if (!hasFootnoteReference)
+ {
+ var firstPara = tempElement.Descendants(W.p).FirstOrDefault();
+ if (firstPara != null)
+ {
+ var firstRun = firstPara.Descendants(W.r).FirstOrDefault();
+ if (firstRun != null)
+ {
+ if (fn.ContentElement.Name == W.footnoteReference)
+ firstRun.AddBeforeSelf(
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "FootnoteReference"))),
+ new XElement(W.footnoteRef)));
+ else
+ firstRun.AddBeforeSelf(
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "EndnoteReference"))),
+ new XElement(W.endnoteRef)));
+ }
+ }
+ }
+ XElement newTempElement = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(tempElement);
+ var newContentElement = newTempElement.Descendants().FirstOrDefault(d => d.Name == W.footnote || d.Name == W.endnote);
+ if (newContentElement == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ footnoteEndnoteBefore.ReplaceNodes(newContentElement.Nodes());
+ }
+ }
+ else
+ throw new OpenXmlPowerToolsException("Internal error");
+ }
+ }
+
+ private static void RectifyFootnoteEndnoteIds(
+ MainDocumentPart mainDocumentPartBefore,
+ MainDocumentPart mainDocumentPartAfter,
+ MainDocumentPart mainDocumentPartWithRevisions,
+ XDocument mainDocumentXDoc,
+ WmlComparerSettings settings)
+ {
+ var footnotesPartBefore = mainDocumentPartBefore.FootnotesPart;
+ var endnotesPartBefore = mainDocumentPartBefore.EndnotesPart;
+ var footnotesPartAfter = mainDocumentPartAfter.FootnotesPart;
+ var endnotesPartAfter = mainDocumentPartAfter.EndnotesPart;
+ var footnotesPartWithRevisions = mainDocumentPartWithRevisions.FootnotesPart;
+ var endnotesPartWithRevisions = mainDocumentPartWithRevisions.EndnotesPart;
+
+ XDocument footnotesPartBeforeXDoc = null;
+ if (footnotesPartBefore != null)
+ footnotesPartBeforeXDoc = footnotesPartBefore.GetXDocument();
+
+ XDocument footnotesPartAfterXDoc = null;
+ if (footnotesPartAfter != null)
+ footnotesPartAfterXDoc = footnotesPartAfter.GetXDocument();
+
+ XDocument footnotesPartWithRevisionsXDoc = null;
+ if (footnotesPartWithRevisions != null)
+ {
+ footnotesPartWithRevisionsXDoc = footnotesPartWithRevisions.GetXDocument();
+ footnotesPartWithRevisionsXDoc
+ .Root
+ .Elements(W.footnote)
+ .Where(e => (string)e.Attribute(W.id) != "-1" && (string)e.Attribute(W.id) != "0")
+ .Remove();
+ }
+
+ XDocument endnotesPartBeforeXDoc = null;
+ if (endnotesPartBefore != null)
+ endnotesPartBeforeXDoc = endnotesPartBefore.GetXDocument();
+
+ XDocument endnotesPartAfterXDoc = null;
+ if (endnotesPartAfter != null)
+ endnotesPartAfterXDoc = endnotesPartAfter.GetXDocument();
+
+ XDocument endnotesPartWithRevisionsXDoc = null;
+ if (endnotesPartWithRevisions != null)
+ {
+ endnotesPartWithRevisionsXDoc = endnotesPartWithRevisions.GetXDocument();
+ endnotesPartWithRevisionsXDoc
+ .Root
+ .Elements(W.endnote)
+ .Where(e => (string)e.Attribute(W.id) != "-1" && (string)e.Attribute(W.id) != "0")
+ .Remove();
+ }
+
+ var footnotesRefs = mainDocumentXDoc
+ .Descendants(W.footnoteReference)
+ .Select((fn, idx) =>
+ {
+ return new
+ {
+ FootNote = fn,
+ Idx = idx,
+ };
+ });
+
+ foreach (var fn in footnotesRefs)
+ {
+ var oldId = (string)fn.FootNote.Attribute(W.id);
+ var newId = (fn.Idx + 1).ToString();
+ fn.FootNote.Attribute(W.id).Value = newId;
+ var footnote = footnotesPartAfterXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(e => (string)e.Attribute(W.id) == oldId);
+ if (footnote == null)
+ {
+ footnote = footnotesPartBeforeXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(e => (string)e.Attribute(W.id) == oldId);
+ }
+ if (footnote == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var cloned = new XElement(footnote);
+ cloned.Attribute(W.id).Value = newId;
+ footnotesPartWithRevisionsXDoc
+ .Root
+ .Add(cloned);
+ }
+
+ var endnotesRefs = mainDocumentXDoc
+ .Descendants(W.endnoteReference)
+ .Select((fn, idx) =>
+ {
+ return new
+ {
+ Endnote = fn,
+ Idx = idx,
+ };
+ });
+
+ foreach (var fn in endnotesRefs)
+ {
+ var oldId = (string)fn.Endnote.Attribute(W.id);
+ var newId = (fn.Idx + 1).ToString();
+ fn.Endnote.Attribute(W.id).Value = newId;
+ var endnote = endnotesPartAfterXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(e => (string)e.Attribute(W.id) == oldId);
+ if (endnote == null)
+ {
+ endnote = endnotesPartBeforeXDoc
+ .Root
+ .Elements()
+ .FirstOrDefault(e => (string)e.Attribute(W.id) == oldId);
+ }
+ if (endnote == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var cloned = new XElement(endnote);
+ cloned.Attribute(W.id).Value = newId;
+ endnotesPartWithRevisionsXDoc
+ .Root
+ .Add(cloned);
+ }
+
+ if (footnotesPartWithRevisionsXDoc != null)
+ {
+ MarkContentAsDeletedOrInserted(footnotesPartWithRevisionsXDoc, settings);
+ CoalesceAdjacentRunsWithIdenticalFormatting(footnotesPartWithRevisionsXDoc);
+ XElement newXDocRoot = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(footnotesPartWithRevisionsXDoc.Root);
+ footnotesPartWithRevisionsXDoc.Root.ReplaceWith(newXDocRoot);
+ IgnorePt14Namespace(footnotesPartWithRevisionsXDoc.Root);
+ footnotesPartWithRevisions.PutXDocument();
+ }
+ if (endnotesPartWithRevisionsXDoc != null)
+ {
+ MarkContentAsDeletedOrInserted(endnotesPartWithRevisionsXDoc, settings);
+ CoalesceAdjacentRunsWithIdenticalFormatting(endnotesPartWithRevisionsXDoc);
+ XElement newXDocRoot = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(endnotesPartWithRevisionsXDoc.Root);
+ endnotesPartWithRevisionsXDoc.Root.ReplaceWith(newXDocRoot);
+ IgnorePt14Namespace(endnotesPartWithRevisionsXDoc.Root);
+ endnotesPartWithRevisions.PutXDocument();
+ }
+ }
+
+ /// Here is the crux of the fix to the algorithm. After assembling the entire list of ComparisonUnitAtoms, we do the following:
+ /// - First, figure out the maximum hierarchy depth, considering only paragraphs, txbx, txbxContent, tables, rows, cells, and content controls.
+ /// - For documents that do not contain tables, nor text boxes, this maximum hierarchy depth will always be 1.
+ /// - For atoms within a table, the depth will be 4. The first level is the table, the second level is row, third is cell, fourth is paragraph.
+ /// - For atoms within a nested table, the depth will be 7: Table / Row / Cell / Table / Row / Cell / Paragraph
+ /// - For atoms within a text box, the depth will be 3: Paragraph / txbxContent / Paragraph
+ /// - For atoms within a table in a text box, the depth will be 5: Paragraph / txbxContent / Table / Row / Cell / Paragraph
+ /// In any case, we figure out the maximum depth.
+ ///
+ /// Then we iterate through the list of content atoms backwards. We do this n times, where n is the maximum depth.
+ ///
+ /// At each level, we find a paragraph mark, and working backwards, we set the guids in the hierarchy so that the content will be assembled together correctly.
+ ///
+ /// For each iteration, we only set unids at the level that we are working at.
+ ///
+ /// So first we will set all unids at level 1. When we find a paragraph mark, we get the unid for that level, and then working backwards, until we find another
+ /// paragraph mark, we set all unids at level 1 to the same unid as level 1 of the paragraph mark.
+ ///
+ /// Then we set all unids at level 2. When we find a paragraph mark, we get the unid for that level, and then working backwards, until we find another paragraph
+ /// mark, we set all unids at level 2 to the same unid as level 2 of the paragraph mark. At some point, we will find a paragraph mark with no level 2. This is
+ /// not a problem. We stop setting anything until we find another paragraph mark that has a level 2, at which point we resume setting values at level 2.
+ ///
+ /// Same process for level 3, and so on, until we have processed to the maximum depth of the hierarchy.
+ ///
+ /// At the end of this process, we will be able to do the coalsce recurse algorithm, and the content atom list will be put back together into a beautiful tree,
+ /// where every element is correctly positioned in the hierarchy.
+ ///
+ /// This should also properly assemble the test where just the paragraph marks have been deleted for a range of paragraphs.
+ ///
+ /// There is an interesting thought - it is possible that I have set two runs of text that were initially in the same paragraph, but then after
+ /// processing, they match up to text in different paragraphs. Therefore this will not work. We need to actually keep a list of reconstructed ancestor
+ /// Unids, because the same paragraph would get set to two different IDs - two ComparisonUnitAtoms need to be in separate paragraphs in the reconstructed
+ /// document, but their ancestors actually point to the same paragraph.
+ ///
+ /// Fix this in the algorithm, and also keep the appropriate list in ComparisonUnitAtom class.
+
+ private static void AssembleAncestorUnidsInOrderToRebuildXmlTreeProperly(List<ComparisonUnitAtom> comparisonUnitAtomList)
+ {
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in comparisonUnitAtomList)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // the following loop sets all ancestor unids in the after document to the unids in the before document for all pPr where the status is equal.
+ // this should always be true.
+
+ // one additional modification to make to this loop - where we find a pPr in a text box, we want to do this as well, regardless of whether the status is equal, inserted, or deleted.
+ // reason being that this module does not support insertion / deletion of text boxes themselves. If a text box is in the before or after document, it will be in the document that
+ // contains deltas. It may have inserted or deleted text, but regardless, it will be in the result document.
+ foreach (var cua in comparisonUnitAtomList)
+ {
+ var doSet = false;
+ if (cua.ContentElement.Name == W.pPr)
+ {
+ if (cua.AncestorElements.Any(ae => ae.Name == W.txbxContent))
+ doSet = true;
+ if (cua.CorrelationStatus == CorrelationStatus.Equal)
+ doSet = true;
+ }
+ if (doSet)
+ {
+ var cuaBefore = cua.ComparisonUnitAtomBefore;
+ var ancestorsAfter = cua.AncestorElements;
+ if (cuaBefore != null)
+ {
+ var ancestorsBefore = cuaBefore.AncestorElements;
+ if (ancestorsAfter.Length == ancestorsBefore.Length)
+ {
+ var zipped = ancestorsBefore.Zip(ancestorsAfter, (b, a) =>
+ new
+ {
+ After = a,
+ Before = b,
+ });
+
+ foreach (var z in zipped)
+ {
+ var afterUnidAtt = z.After.Attribute(PtOpenXml.Unid);
+ var beforeUnidAtt = z.Before.Attribute(PtOpenXml.Unid);
+ if (afterUnidAtt != null && beforeUnidAtt != null)
+ afterUnidAtt.Value = beforeUnidAtt.Value;
+ }
+ }
+ }
+ }
+ }
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in comparisonUnitAtomList)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var rComparisonUnitAtomList = ((IEnumerable<ComparisonUnitAtom>)comparisonUnitAtomList).Reverse().ToList();
+
+ // the following should always succeed, because there will always be at least one element in rComparisonUnitAtomList, and there will always be at least one
+ // ancestor in AncestorElements
+ var deepestAncestor = rComparisonUnitAtomList.First().AncestorElements.First();
+ var deepestAncestorName = deepestAncestor.Name;
+ string deepestAncestorUnid = null;
+ if (deepestAncestorName == W.footnote || deepestAncestorName == W.endnote)
+ {
+ deepestAncestorUnid = (string)deepestAncestor.Attribute(PtOpenXml.Unid);
+ }
+
+ /// If the following loop finds a pPr that is in a text box, then continue on, processing the pPr and all of its contents as though it were
+ /// content in the containing text box. This is going to leave it after this loop where the AncestorUnids for the content in the text box will be
+ /// incomplete. We then will need to go through the rComparisonUnitAtomList a second time, processing all of the text boxes.
+
+ /// Note that this makes the basic assumption that a text box can't be nested inside of a text box, which, as far as I know, is a good assumption.
+
+ /// This also makes the basic assumption that an endnote / footnote can't contain a text box, which I believe is a good assumption.
+
+
+ string[] currentAncestorUnids = null;
+ foreach (var cua in rComparisonUnitAtomList)
+ {
+ if (cua.ContentElement.Name == W.pPr)
+ {
+ var pPr_inTextBox = cua
+ .AncestorElements
+ .Any(ae => ae.Name == W.txbxContent);
+
+ if (!pPr_inTextBox)
+ {
+ // this will collect the ancestor unids for the paragraph.
+ // my hypothesis is that these ancestor unids should be the same for all content unit atoms within that paragraph.
+ currentAncestorUnids = cua
+ .AncestorElements
+ .Select(ae =>
+ {
+ var thisUnid = (string)ae.Attribute(PtOpenXml.Unid);
+ if (thisUnid == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ return thisUnid;
+ })
+ .ToArray();
+ cua.AncestorUnids = currentAncestorUnids;
+ if (deepestAncestorUnid != null)
+ cua.AncestorUnids[0] = deepestAncestorUnid;
+ continue;
+ }
+ }
+
+ var thisDepth = cua.AncestorElements.Length;
+ var additionalAncestorUnids = cua
+ .AncestorElements
+ .Skip(currentAncestorUnids.Length)
+ .Select(ae =>
+ {
+ var thisUnid = (string)ae.Attribute(PtOpenXml.Unid);
+ if (thisUnid == null)
+ Guid.NewGuid().ToString().Replace("-", "");
+ return thisUnid;
+ });
+ var thisAncestorUnids = currentAncestorUnids
+ .Concat(additionalAncestorUnids)
+ .ToArray();
+ cua.AncestorUnids = thisAncestorUnids;
+ if (deepestAncestorUnid != null)
+ cua.AncestorUnids[0] = deepestAncestorUnid;
+ }
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in comparisonUnitAtomList)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ // this is the second loop that processes all text boxes.
+ currentAncestorUnids = null;
+ bool skipUntilNextPpr = false;
+ foreach (var cua in rComparisonUnitAtomList)
+ {
+ if (currentAncestorUnids != null && cua.AncestorElements.Length < currentAncestorUnids.Length)
+ {
+ skipUntilNextPpr = true;
+ currentAncestorUnids = null;
+ continue;
+ }
+ if (cua.ContentElement.Name == W.pPr)
+ {
+ //if (s_True)
+ //{
+ // var sb = new StringBuilder();
+ // foreach (var item in comparisonUnitAtomList)
+ // sb.Append(item.ToString()).Append(Environment.NewLine);
+ // var sbs = sb.ToString();
+ // TestUtil.NotePad(sbs);
+ //}
+
+ var pPr_inTextBox = cua
+ .AncestorElements
+ .Any(ae => ae.Name == W.txbxContent);
+
+ if (!pPr_inTextBox)
+ {
+ skipUntilNextPpr = true;
+ currentAncestorUnids = null;
+ continue;
+ }
+ else
+ {
+ skipUntilNextPpr = false;
+
+ currentAncestorUnids = cua
+ .AncestorElements
+ .Select(ae =>
+ {
+ var thisUnid = (string)ae.Attribute(PtOpenXml.Unid);
+ if (thisUnid == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ return thisUnid;
+ })
+ .ToArray();
+ cua.AncestorUnids = currentAncestorUnids;
+ continue;
+ }
+ }
+
+ if (skipUntilNextPpr)
+ continue;
+
+ var thisDepth = cua.AncestorElements.Length;
+ var additionalAncestorUnids = cua
+ .AncestorElements
+ .Skip(currentAncestorUnids.Length)
+ .Select(ae =>
+ {
+ var thisUnid = (string)ae.Attribute(PtOpenXml.Unid);
+ if (thisUnid == null)
+ Guid.NewGuid().ToString().Replace("-", "");
+ return thisUnid;
+ });
+ var thisAncestorUnids = currentAncestorUnids
+ .Concat(additionalAncestorUnids)
+ .ToArray();
+ cua.AncestorUnids = thisAncestorUnids;
+ }
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in comparisonUnitAtomList)
+ sb.Append(item.ToStringAncestorUnids()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+ }
+
+ // the following gets a flattened list of ComparisonUnitAtoms, with status indicated in each ComparisonUnitAtom: Deleted, Inserted, or Equal
+ private static List<ComparisonUnitAtom> FlattenToComparisonUnitAtomList(List<CorrelatedSequence> correlatedSequence, WmlComparerSettings settings)
+ {
+ var listOfComparisonUnitAtoms = correlatedSequence
+ .Select(cs =>
+ {
+
+ // need to write some code here to find out if we are assembling a paragraph (or anything) that contains the following unid.
+ // why do are we dropping content???????
+ //string searchFor = "0ecb9184";
+
+
+
+
+
+
+
+
+
+
+
+ if (cs.CorrelationStatus == CorrelationStatus.Equal)
+ {
+ var contentAtomsBefore = cs
+ .ComparisonUnitArray1
+ .Select(ca => ca.DescendantContentAtoms())
+ .SelectMany(m => m);
+
+ var contentAtomsAfter = cs
+ .ComparisonUnitArray2
+ .Select(ca => ca.DescendantContentAtoms())
+ .SelectMany(m => m);
+
+ var comparisonUnitAtomList = contentAtomsBefore
+ .Zip(contentAtomsAfter,
+ (before, after) =>
+ {
+ return new ComparisonUnitAtom(after.ContentElement, after.AncestorElements, after.Part, settings)
+ {
+ CorrelationStatus = CorrelationStatus.Equal,
+ ContentElementBefore = before.ContentElement,
+ ComparisonUnitAtomBefore = before,
+ };
+ })
+ .ToList();
+ return comparisonUnitAtomList;
+ }
+ else if (cs.CorrelationStatus == CorrelationStatus.Deleted)
+ {
+ var comparisonUnitAtomList = cs
+ .ComparisonUnitArray1
+ .Select(ca => ca.DescendantContentAtoms())
+ .SelectMany(m => m)
+ .Select(ca =>
+ new ComparisonUnitAtom(ca.ContentElement, ca.AncestorElements, ca.Part, settings)
+ {
+ CorrelationStatus = CorrelationStatus.Deleted,
+ });
+
+ return comparisonUnitAtomList;
+ }
+ else if (cs.CorrelationStatus == CorrelationStatus.Inserted)
+ {
+ var comparisonUnitAtomList = cs
+ .ComparisonUnitArray2
+ .Select(ca => ca.DescendantContentAtoms())
+ .SelectMany(m => m)
+ .Select(ca =>
+ new ComparisonUnitAtom(ca.ContentElement, ca.AncestorElements, ca.Part, settings)
+ {
+ CorrelationStatus = CorrelationStatus.Inserted,
+ });
+ return comparisonUnitAtomList;
+ }
+ else
+ throw new OpenXmlPowerToolsException("Internal error");
+ })
+ .SelectMany(m => m)
+ .ToList();
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in listOfComparisonUnitAtoms)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ return listOfComparisonUnitAtoms;
+ }
+
+ // for any deleted or inserted rows, we go into the w:trPr properties, and add the appropriate w:ins or w:del element, and therefore
+ // when generating the document, the appropriate row will be marked as deleted or inserted.
+ private static void MarkRowsAsDeletedOrInserted(WmlComparerSettings settings, List<CorrelatedSequence> correlatedSequence)
+ {
+ foreach (var dcs in correlatedSequence.Where(cs =>
+ cs.CorrelationStatus == CorrelationStatus.Deleted || cs.CorrelationStatus == CorrelationStatus.Inserted))
+ {
+ // iterate through all deleted/inserted items in dcs.ComparisonUnitArray1/ComparisonUnitArray2
+ var toIterateThrough = dcs.ComparisonUnitArray1;
+ if (dcs.CorrelationStatus == CorrelationStatus.Inserted)
+ toIterateThrough = dcs.ComparisonUnitArray2;
+
+ foreach (var ca in toIterateThrough)
+ {
+ var cug = ca as ComparisonUnitGroup;
+
+ // this works because we will never see a table in this list, only rows. If tables were in this list, would need to recursively
+ // go into children, but tables are always flattened in the LCS process.
+
+ // when we have a row, it is only necessary to find the first content atom of the row, then find the row ancestor, and then tweak
+ // the w:trPr
+
+ if (cug != null && cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Row)
+ {
+ var firstContentAtom = cug.DescendantContentAtoms().FirstOrDefault();
+ if (firstContentAtom == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var tr = firstContentAtom
+ .AncestorElements
+ .Reverse()
+ .FirstOrDefault(a => a.Name == W.tr);
+
+ if (tr == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var trPr = tr.Element(W.trPr);
+ if (trPr == null)
+ {
+ trPr = new XElement(W.trPr);
+ tr.AddFirst(trPr);
+ }
+ XName revTrackElementName = null;
+ if (dcs.CorrelationStatus == CorrelationStatus.Deleted)
+ revTrackElementName = W.del;
+ else if (dcs.CorrelationStatus == CorrelationStatus.Inserted)
+ revTrackElementName = W.ins;
+ trPr.Add(new XElement(revTrackElementName,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions)));
+ }
+ }
+ }
+ }
+
+ public enum WmlComparerRevisionType
+ {
+ Inserted,
+ Deleted,
+ }
+
+ public class WmlComparerRevision
+ {
+ public WmlComparerRevisionType RevisionType;
+ public string Text;
+ public string Author;
+ public string Date;
+ public XElement ContentXElement;
+ public XElement RevisionXElement;
+ public Uri PartUri;
+ public string PartContentType;
+ }
+
+ private static XName[] RevElementsWithNoText = new XName[] {
+ M.oMath,
+ M.oMathPara,
+ W.drawing,
+ };
+
+ public static List<WmlComparerRevision> GetRevisions(WmlDocument source, WmlComparerSettings settings)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(source.DocumentByteArray, 0, source.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ TestForInvalidContent(wDoc);
+ RemoveExistingPowerToolsMarkup(wDoc);
+
+ var contentParent = wDoc.MainDocumentPart.GetXDocument().Root.Element(W.body);
+ var atomList = WmlComparer.CreateComparisonUnitAtomList(wDoc.MainDocumentPart, contentParent, settings).ToArray();
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in atomList)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var grouped = atomList
+ .GroupAdjacent(a =>
+ {
+ var key = a.CorrelationStatus.ToString();
+ if (a.CorrelationStatus != CorrelationStatus.Equal)
+ {
+ var rt = new XElement(a.RevTrackElement.Name,
+ new XAttribute(XNamespace.Xmlns + "w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"),
+ a.RevTrackElement.Attributes().Where(a2 => a2.Name != W.id && a2.Name != PtOpenXml.Unid));
+ key += rt.ToString(SaveOptions.DisableFormatting);
+ }
+ return key;
+ })
+ .ToList();
+
+ var revisions = grouped
+ .Where(k => k.Key != "Equal")
+ .ToList();
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in revisions)
+ sb.Append(item.Key + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var mainDocPartRevisionList = revisions
+ .Select(rg =>
+ {
+ var rev = new WmlComparerRevision();
+ if (rg.Key.StartsWith("Inserted"))
+ rev.RevisionType = WmlComparerRevisionType.Inserted;
+ else if (rg.Key.StartsWith("Deleted"))
+ rev.RevisionType = WmlComparerRevisionType.Deleted;
+ var revTrackElement = rg.First().RevTrackElement;
+ rev.RevisionXElement = revTrackElement;
+ rev.Author = (string)revTrackElement.Attribute(W.author);
+ rev.ContentXElement = rg.First().ContentElement;
+ rev.Date = (string)revTrackElement.Attribute(W.date);
+ rev.PartUri = wDoc.MainDocumentPart.Uri;
+ rev.PartContentType = wDoc.MainDocumentPart.ContentType;
+ if (!RevElementsWithNoText.Contains(rev.ContentXElement.Name))
+ {
+ rev.Text = rg
+ .Select(rgc =>
+ {
+ if (rgc.ContentElement.Name == W.pPr)
+ return Environment.NewLine;
+ return rgc.ContentElement.Value;
+ })
+ .StringConcatenate();
+ }
+ return rev;
+ })
+ .ToList();
+
+ var footnotesRevisionList = GetFootnoteEndnoteRevisionList(wDoc.MainDocumentPart.FootnotesPart, W.footnote, settings);
+ var endnotesRevisionList = GetFootnoteEndnoteRevisionList(wDoc.MainDocumentPart.EndnotesPart, W.endnote, settings);
+ var finalRevisionList = mainDocPartRevisionList.Concat(footnotesRevisionList).Concat(endnotesRevisionList).ToList();
+ return finalRevisionList;
+ }
+ }
+ }
+
+ private static IEnumerable<WmlComparerRevision> GetFootnoteEndnoteRevisionList(OpenXmlPart footnotesEndnotesPart,
+ XName footnoteEndnoteElementName,
+ WmlComparerSettings settings)
+ {
+ if (footnotesEndnotesPart == null)
+ return Enumerable.Empty<WmlComparerRevision>();
+
+ var xDoc = footnotesEndnotesPart.GetXDocument();
+ var footnotesEndnotes = xDoc.Root.Elements(footnoteEndnoteElementName);
+ List<WmlComparerRevision> revisionsForPart = new List<WmlComparerRevision>();
+ foreach (var fn in footnotesEndnotes)
+ {
+ var atomList = WmlComparer.CreateComparisonUnitAtomList(footnotesEndnotesPart, fn, settings).ToArray();
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in atomList)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var grouped = atomList
+ .GroupAdjacent(a =>
+ {
+ var key = a.CorrelationStatus.ToString();
+ if (a.CorrelationStatus != CorrelationStatus.Equal)
+ {
+ var rt = new XElement(a.RevTrackElement.Name,
+ new XAttribute(XNamespace.Xmlns + "w", "http://schemas.openxmlformats.org/wordprocessingml/2006/main"),
+ a.RevTrackElement.Attributes().Where(a2 => a2.Name != W.id && a2.Name != PtOpenXml.Unid));
+ key += rt.ToString(SaveOptions.DisableFormatting);
+ }
+ return key;
+ })
+ .ToList();
+
+ var revisions = grouped
+ .Where(k => k.Key != "Equal")
+ .ToList();
+
+ var thisNoteRevisionList = revisions
+ .Select(rg =>
+ {
+ var rev = new WmlComparerRevision();
+ if (rg.Key.StartsWith("Inserted"))
+ rev.RevisionType = WmlComparerRevisionType.Inserted;
+ else if (rg.Key.StartsWith("Deleted"))
+ rev.RevisionType = WmlComparerRevisionType.Deleted;
+ var revTrackElement = rg.First().RevTrackElement;
+ rev.RevisionXElement = revTrackElement;
+ rev.Author = (string)revTrackElement.Attribute(W.author);
+ rev.ContentXElement = rg.First().ContentElement;
+ rev.Date = (string)revTrackElement.Attribute(W.date);
+ rev.PartUri = footnotesEndnotesPart.Uri;
+ rev.PartContentType = footnotesEndnotesPart.ContentType;
+ if (!RevElementsWithNoText.Contains(rev.ContentXElement.Name))
+ {
+ rev.Text = rg
+ .Select(rgc =>
+ {
+ if (rgc.ContentElement.Name == W.pPr)
+ return Environment.NewLine;
+ return rgc.ContentElement.Value;
+ })
+ .StringConcatenate();
+ }
+ return rev;
+ });
+
+ foreach (var item in thisNoteRevisionList)
+ revisionsForPart.Add(item);
+ }
+ return revisionsForPart;
+ }
+
+ // prohibit
+ // - altChunk
+ // - subDoc
+ // - contentPart
+ private static void TestForInvalidContent(WordprocessingDocument wDoc)
+ {
+ foreach (var part in wDoc.ContentParts())
+ {
+ var xDoc = part.GetXDocument();
+ if (xDoc.Descendants(W.altChunk).Any())
+ throw new OpenXmlPowerToolsException("Unsupported document, contains w:altChunk");
+ if (xDoc.Descendants(W.subDoc).Any())
+ throw new OpenXmlPowerToolsException("Unsupported document, contains w:subDoc");
+ if (xDoc.Descendants(W.contentPart).Any())
+ throw new OpenXmlPowerToolsException("Unsupported document, contains w:contentPart");
+ }
+ }
+
+ private static void RemoveExistingPowerToolsMarkup(WordprocessingDocument wDoc)
+ {
+ wDoc.MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Descendants()
+ .Attributes()
+ .Where(a => a.Name.Namespace == PtOpenXml.pt)
+ .Where(a => a.Name != PtOpenXml.Unid)
+ .Remove();
+ wDoc.MainDocumentPart.PutXDocument();
+
+ var fnPart = wDoc.MainDocumentPart.FootnotesPart;
+ if (fnPart != null)
+ {
+ var fnXDoc = fnPart.GetXDocument();
+ fnXDoc
+ .Root
+ .Descendants()
+ .Attributes()
+ .Where(a => a.Name.Namespace == PtOpenXml.pt)
+ .Where(a => a.Name != PtOpenXml.Unid)
+ .Remove();
+ fnPart.PutXDocument();
+ }
+
+ var enPart = wDoc.MainDocumentPart.EndnotesPart;
+ if (enPart != null)
+ {
+ var enXDoc = enPart.GetXDocument();
+ enXDoc
+ .Root
+ .Descendants()
+ .Attributes()
+ .Where(a => a.Name.Namespace == PtOpenXml.pt)
+ .Where(a => a.Name != PtOpenXml.Unid)
+ .Remove();
+ enPart.PutXDocument();
+ }
+ }
+
+ private static void AddSha1HashToBlockLevelContent(OpenXmlPart part, XElement contentParent, WmlComparerSettings settings)
+ {
+ var blockLevelContentToAnnotate = contentParent
+ .Descendants()
+ .Where(d => ElementsToHaveSha1Hash.Contains(d.Name));
+
+ foreach (var blockLevelContent in blockLevelContentToAnnotate)
+ {
+ var cloneBlockLevelContentForHashing = (XElement)CloneBlockLevelContentForHashing(part, blockLevelContent, true, settings);
+ var shaString = cloneBlockLevelContentForHashing.ToString(SaveOptions.DisableFormatting)
+ .Replace(" xmlns=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"", "");
+ var sha1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(shaString);
+ blockLevelContent.Add(new XAttribute(PtOpenXml.SHA1Hash, sha1Hash));
+
+ if (blockLevelContent.Name == W.tbl ||
+ blockLevelContent.Name == W.tr)
+ {
+ var clonedForStructureHash = (XElement)CloneForStructureHash(cloneBlockLevelContentForHashing);
+
+ // this is a convenient place to look at why tables are being compared as different.
+
+ //if (blockLevelContent.Name == W.tbl)
+ // Console.WriteLine();
+
+ var shaString2 = clonedForStructureHash.ToString(SaveOptions.DisableFormatting)
+ .Replace(" xmlns=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"", "");
+ var sha1Hash2 = WmlComparerUtil.SHA1HashStringForUTF8String(shaString2);
+ blockLevelContent.Add(new XAttribute(PtOpenXml.StructureSHA1Hash, sha1Hash2));
+ }
+ }
+ }
+
+ // This strips all text nodes from the XML tree, thereby leaving only the structure.
+ private static object CloneForStructureHash(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Elements().Select(e => CloneForStructureHash(e)));
+ }
+ return null;
+ }
+
+ static XName[] AttributesToTrimWhenCloning = new XName[] {
+ WP14.anchorId,
+ WP14.editId,
+ "ObjectID",
+ "ShapeID",
+ "id",
+ "type",
+ };
+
+ private static object CloneBlockLevelContentForHashing(OpenXmlPart mainDocumentPart, XNode node, bool includeRelatedParts, WmlComparerSettings settings)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.bookmarkStart ||
+ element.Name == W.bookmarkEnd ||
+ element.Name == W.pPr ||
+ element.Name == W.rPr)
+ return null;
+
+ if (element.Name == W.p)
+ {
+ var clonedPara = new XElement(element.Name,
+ element.Attributes().Where(a => a.Name != W.rsid &&
+ a.Name != W.rsidDel &&
+ a.Name != W.rsidP &&
+ a.Name != W.rsidR &&
+ a.Name != W.rsidRDefault &&
+ a.Name != W.rsidRPr &&
+ a.Name != W.rsidSect &&
+ a.Name != W.rsidTr &&
+ a.Name.Namespace != PtOpenXml.pt),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+
+ var groupedRuns = clonedPara
+ .Elements()
+ .GroupAdjacent(e => e.Name == W.r &&
+ e.Elements().Count() == 1 &&
+ e.Element(W.t) != null);
+
+ var clonedParaWithGroupedRuns = new XElement(element.Name,
+ groupedRuns.Select(g =>
+ {
+ if (g.Key)
+ {
+ var text = g.Select(t => t.Value).StringConcatenate();
+ if (settings.CaseInsensitive)
+ text = text.ToUpper(settings.CultureInfo);
+ var newRun = (object)new XElement(W.r,
+ new XElement(W.t,
+ text));
+ return newRun;
+ }
+ return g;
+ }));
+
+ return clonedParaWithGroupedRuns;
+ }
+
+ if (element.Name == W.r)
+ {
+ var clonedRuns = element
+ .Elements()
+ .Where(e => e.Name != W.rPr)
+ .Select(rc => new XElement(W.r, CloneBlockLevelContentForHashing(mainDocumentPart, rc, includeRelatedParts, settings)));
+ return clonedRuns;
+ }
+
+ if (element.Name == W.tbl)
+ {
+ var clonedTable = new XElement(W.tbl,
+ element.Elements(W.tr).Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return clonedTable;
+ }
+
+ if (element.Name == W.tr)
+ {
+ var clonedRow = new XElement(W.tr,
+ element.Elements(W.tc).Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return clonedRow;
+ }
+
+ if (element.Name == W.tc)
+ {
+ var clonedCell = new XElement(W.tc,
+ element.Elements().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return clonedCell;
+ }
+
+ if (element.Name == W.tcPr)
+ {
+ var clonedCellProps = new XElement(W.tcPr,
+ element.Elements(W.gridSpan).Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return clonedCellProps;
+ }
+
+ if (element.Name == W.gridSpan)
+ {
+ var clonedGridSpan = new XElement(W.gridSpan,
+ new XAttribute("val", (string)element.Attribute(W.val)));
+ return clonedGridSpan;
+ }
+
+ if (element.Name == W.txbxContent)
+ {
+ var clonedTextbox = new XElement(W.txbxContent,
+ element.Elements().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return clonedTextbox;
+ }
+
+ if (includeRelatedParts)
+ {
+ if (ComparisonUnitWord.s_ElementsWithRelationshipIds.Contains(element.Name))
+ {
+ var newElement = new XElement(element.Name,
+ element.Attributes()
+ .Where(a => a.Name.Namespace != PtOpenXml.pt)
+ .Where(a => !AttributesToTrimWhenCloning.Contains(a.Name))
+ .Select(a =>
+ {
+ if (!ComparisonUnitWord.s_RelationshipAttributeNames.Contains(a.Name))
+ return a;
+ var rId = (string)a;
+
+ // could be an hyperlink relationship
+ try
+ {
+ OpenXmlPart oxp = mainDocumentPart.GetPartById(rId);
+ if (oxp == null)
+ throw new FileFormatException("Invalid WordprocessingML Document");
+ var anno = oxp.Annotation<PartSHA1HashAnnotation>();
+ if (anno != null)
+ return new XAttribute(a.Name, anno.Hash);
+
+ if (!oxp.ContentType.EndsWith("xml"))
+ {
+ using (var str = oxp.GetStream())
+ {
+ byte[] ba;
+ using (BinaryReader br = new BinaryReader(str))
+ {
+ ba = br.ReadBytes((int)str.Length);
+ }
+ var sha1 = WmlComparerUtil.SHA1HashStringForByteArray(ba);
+ oxp.AddAnnotation(new PartSHA1HashAnnotation(sha1));
+ return new XAttribute(a.Name, sha1);
+ }
+ }
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ HyperlinkRelationship hr = mainDocumentPart.HyperlinkRelationships.FirstOrDefault(z => z.Id == rId);
+ if (hr != null)
+ {
+ var str = hr.Uri.ToString();
+ return new XAttribute(a.Name, str);
+ }
+ // could be an external relationship
+ ExternalRelationship er = mainDocumentPart.ExternalRelationships.FirstOrDefault(z => z.Id == rId);
+ if (er != null)
+ {
+ var str = er.Uri.ToString();
+ return new XAttribute(a.Name, str);
+ }
+ return new XAttribute(a.Name, "NULL Relationship");
+ }
+
+ return null;
+ }),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return newElement;
+ }
+ }
+
+ if (element.Name == VML.shape)
+ {
+ return new XElement(element.Name,
+ element.Attributes()
+ .Where(a => a.Name.Namespace != PtOpenXml.pt)
+ .Where(a => a.Name != "style" && a.Name != "id" && a.Name != "type"),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ }
+
+ if (element.Name == O.OLEObject)
+ {
+ var o = new XElement(element.Name,
+ element.Attributes()
+ .Where(a => a.Name.Namespace != PtOpenXml.pt)
+ .Where(a => a.Name != "ObjectID" && a.Name != R.id),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return o;
+ }
+
+ if (element.Name == W._object)
+ {
+ var o = new XElement(element.Name,
+ element.Attributes()
+ .Where(a => a.Name.Namespace != PtOpenXml.pt),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ return o;
+ }
+
+ if (element.Name == WP.docPr)
+ {
+ return new XElement(element.Name,
+ element.Attributes()
+ .Where(a => a.Name.Namespace != PtOpenXml.pt && a.Name != "id"),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ }
+
+ return new XElement(element.Name,
+ element.Attributes()
+ .Where(a => a.Name.Namespace != PtOpenXml.pt)
+ .Where(a => !AttributesToTrimWhenCloning.Contains(a.Name)),
+ element.Nodes().Select(n => CloneBlockLevelContentForHashing(mainDocumentPart, n, includeRelatedParts, settings)));
+ }
+ if (settings.CaseInsensitive)
+ {
+ var xt = node as XText;
+ if (xt != null)
+ {
+ var newText = xt.Value.ToUpper(settings.CultureInfo);
+ return new XText(newText);
+ }
+ }
+ return node;
+ }
+
+ private static List<CorrelatedSequence> FindCommonAtBeginningAndEnd(CorrelatedSequence unknown, WmlComparerSettings settings)
+ {
+ int lengthToCompare = Math.Min(unknown.ComparisonUnitArray1.Length, unknown.ComparisonUnitArray2.Length);
+
+ var countCommonAtBeginning = unknown
+ .ComparisonUnitArray1
+ .Take(lengthToCompare)
+ .Zip(unknown.ComparisonUnitArray2,
+ (pu1, pu2) =>
+ {
+ return new
+ {
+ Pu1 = pu1,
+ Pu2 = pu2,
+ };
+ })
+ .TakeWhile(pair => pair.Pu1.SHA1Hash == pair.Pu2.SHA1Hash)
+ .Count();
+
+ if (countCommonAtBeginning != 0 && ((double)countCommonAtBeginning / (double)lengthToCompare) < settings.DetailThreshold)
+ countCommonAtBeginning = 0;
+
+ if (countCommonAtBeginning != 0)
+ {
+ var newSequence = new List<CorrelatedSequence>();
+
+ CorrelatedSequence csEqual = new CorrelatedSequence();
+ csEqual.CorrelationStatus = CorrelationStatus.Equal;
+ csEqual.ComparisonUnitArray1 = unknown
+ .ComparisonUnitArray1
+ .Take(countCommonAtBeginning)
+ .ToArray();
+ csEqual.ComparisonUnitArray2 = unknown
+ .ComparisonUnitArray2
+ .Take(countCommonAtBeginning)
+ .ToArray();
+ newSequence.Add(csEqual);
+
+ var remainingLeft = unknown.ComparisonUnitArray1.Length - countCommonAtBeginning;
+ var remainingRight = unknown.ComparisonUnitArray2.Length - countCommonAtBeginning;
+
+ if (remainingLeft != 0 && remainingRight == 0)
+ {
+ CorrelatedSequence csDeleted = new CorrelatedSequence();
+ csDeleted.CorrelationStatus = CorrelationStatus.Deleted;
+ csDeleted.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Skip(countCommonAtBeginning).ToArray();
+ csDeleted.ComparisonUnitArray2 = null;
+ newSequence.Add(csDeleted);
+ }
+ else if (remainingLeft == 0 && remainingRight != 0)
+ {
+ CorrelatedSequence csInserted = new CorrelatedSequence();
+ csInserted.CorrelationStatus = CorrelationStatus.Inserted;
+ csInserted.ComparisonUnitArray1 = null;
+ csInserted.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Skip(countCommonAtBeginning).ToArray();
+ newSequence.Add(csInserted);
+ }
+ else if (remainingLeft != 0 && remainingRight != 0)
+ {
+ var first1 = unknown.ComparisonUnitArray1[0] as ComparisonUnitWord;
+ var first2 = unknown.ComparisonUnitArray2[0] as ComparisonUnitWord;
+
+ if (first1 != null && first2 != null)
+ {
+ // if operating at the word level and
+ // if the last word on the left != pPr && last word on right != pPr
+ // then create an unknown for the rest of the paragraph, and create an unknown for the rest of the unknown
+ // if the last word on the left != pPr and last word on right == pPr
+ // then create deleted for the left, and create an unknown for the rest of the unknown
+ // if the last word on the left == pPr and last word on right != pPr
+ // then create inserted for the right, and create an unknown for the rest of the unknown
+ // if the last word on the left == pPr and last word on right == pPr
+ // then create an unknown for the rest of the unknown
+
+ var remainingInLeft = unknown
+ .ComparisonUnitArray1
+ .Skip(countCommonAtBeginning)
+ .ToArray();
+
+ var remainingInRight = unknown
+ .ComparisonUnitArray2
+ .Skip(countCommonAtBeginning)
+ .ToArray();
+
+ var lastContentAtomLeft = unknown.ComparisonUnitArray1[countCommonAtBeginning - 1].DescendantContentAtoms().FirstOrDefault();
+ var lastContentAtomRight = unknown.ComparisonUnitArray2[countCommonAtBeginning - 1].DescendantContentAtoms().FirstOrDefault();
+
+ if (lastContentAtomLeft.ContentElement.Name != W.pPr && lastContentAtomRight.ContentElement.Name != W.pPr)
+ {
+ var split1 = SplitAtParagraphMark(remainingInLeft);
+ var split2 = SplitAtParagraphMark(remainingInRight);
+ if (split1.Count() == 1 && split2.Count() == 1)
+ {
+ CorrelatedSequence csUnknown2 = new CorrelatedSequence();
+ csUnknown2.CorrelationStatus = CorrelationStatus.Unknown;
+ csUnknown2.ComparisonUnitArray1 = split1.First();
+ csUnknown2.ComparisonUnitArray2 = split2.First();
+ newSequence.Add(csUnknown2);
+ return newSequence;
+ }
+ else if (split1.Count == 2 && split2.Count == 2)
+ {
+ CorrelatedSequence csUnknown2 = new CorrelatedSequence();
+ csUnknown2.CorrelationStatus = CorrelationStatus.Unknown;
+ csUnknown2.ComparisonUnitArray1 = split1.First();
+ csUnknown2.ComparisonUnitArray2 = split2.First();
+ newSequence.Add(csUnknown2);
+
+ CorrelatedSequence csUnknown3 = new CorrelatedSequence();
+ csUnknown3.CorrelationStatus = CorrelationStatus.Unknown;
+ csUnknown3.ComparisonUnitArray1 = split1.Skip(1).First();
+ csUnknown3.ComparisonUnitArray2 = split2.Skip(1).First();
+ newSequence.Add(csUnknown3);
+
+ return newSequence;
+ }
+ }
+ }
+
+ CorrelatedSequence csUnknown = new CorrelatedSequence();
+ csUnknown.CorrelationStatus = CorrelationStatus.Unknown;
+ csUnknown.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Skip(countCommonAtBeginning).ToArray();
+ csUnknown.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Skip(countCommonAtBeginning).ToArray();
+ newSequence.Add(csUnknown);
+ }
+ else if (remainingLeft == 0 && remainingRight == 0)
+ {
+ // nothing to do
+ }
+ return newSequence;
+ }
+
+ // if we get to here, then countCommonAtBeginning == 0
+
+ var countCommonAtEnd = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Take(lengthToCompare)
+ .Zip(unknown
+ .ComparisonUnitArray2
+ .Reverse()
+ .Take(lengthToCompare),
+ (pu1, pu2) =>
+ {
+ return new
+ {
+ Pu1 = pu1,
+ Pu2 = pu2,
+ };
+ })
+ .TakeWhile(pair => pair.Pu1.SHA1Hash == pair.Pu2.SHA1Hash)
+ .Count();
+
+ // never start a common section with a paragraph mark. However, it is OK to set two paragraph marks as equal.
+ while (true)
+ {
+ if (countCommonAtEnd <= 1)
+ break;
+
+ var firstCommon = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Take(countCommonAtEnd)
+ .LastOrDefault();
+
+ var firstCommonWord = firstCommon as ComparisonUnitWord;
+ if (firstCommonWord == null)
+ break;
+
+ // if the word contains more than one atom, then not a paragraph mark
+ if (firstCommonWord.Contents.Count() != 1)
+ break;
+
+ var firstCommonAtom = firstCommonWord.Contents.First() as ComparisonUnitAtom;
+ if (firstCommonAtom == null)
+ break;
+
+ if (firstCommonAtom.ContentElement.Name != W.pPr)
+ break;
+
+ countCommonAtEnd--;
+ }
+
+ bool isOnlyParagraphMark = false;
+ if (countCommonAtEnd == 1)
+ {
+ var firstCommon = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Take(countCommonAtEnd)
+ .LastOrDefault();
+
+ var firstCommonWord = firstCommon as ComparisonUnitWord;
+ if (firstCommonWord != null)
+ {
+ // if the word contains more than one atom, then not a paragraph mark
+ if (firstCommonWord.Contents.Count() == 1)
+ {
+ var firstCommonAtom = firstCommonWord.Contents.First() as ComparisonUnitAtom;
+ if (firstCommonAtom != null)
+ {
+ if (firstCommonAtom.ContentElement.Name == W.pPr)
+ isOnlyParagraphMark = true;
+ }
+ }
+ }
+ }
+ if (countCommonAtEnd == 2)
+ {
+ var firstCommon = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Take(countCommonAtEnd)
+ .LastOrDefault();
+
+ var secondCommon = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Take(countCommonAtEnd)
+ .FirstOrDefault();
+
+ var firstCommonWord = firstCommon as ComparisonUnitWord;
+ var secondCommonWord = secondCommon as ComparisonUnitWord;
+ if (firstCommonWord != null && secondCommonWord != null)
+ {
+ // if the word contains more than one atom, then not a paragraph mark
+ if (firstCommonWord.Contents.Count() == 1 && secondCommonWord.Contents.Count() == 1)
+ {
+ var firstCommonAtom = firstCommonWord.Contents.First() as ComparisonUnitAtom;
+ var secondCommonAtom = secondCommonWord.Contents.First() as ComparisonUnitAtom;
+ if (firstCommonAtom != null && secondCommonAtom != null)
+ {
+ if (secondCommonAtom.ContentElement.Name == W.pPr)
+ isOnlyParagraphMark = true;
+ }
+ }
+ }
+ }
+
+ if (!isOnlyParagraphMark && countCommonAtEnd != 0 && ((double)countCommonAtEnd / (double)lengthToCompare) < settings.DetailThreshold)
+ countCommonAtEnd = 0;
+
+ // If the following test is not there, the test below sets the end paragraph mark of the entire document equal to the end paragraph
+ // mark of the first paragraph in the other document, causing lines to be out of order.
+ // [InlineData("WC010-Para-Before-Table-Unmodified.docx", "WC010-Para-Before-Table-Mod.docx", 3)]
+ if (isOnlyParagraphMark)
+ countCommonAtEnd = 0;
+
+ if (countCommonAtEnd == 0)
+ return null;
+
+ // if countCommonAtEnd != 0, and if it contains a paragraph mark, then if there are comparison units in the same paragraph before the common at end (in either version)
+ // then we want to put all of those comparison units into a single unknown, where they must be resolved against each other. We don't want those comparison units to go into the middle unknown comparison unit.
+
+ if (countCommonAtEnd != 0)
+ {
+ int remainingInLeftParagraph = 0;
+ int remainingInRightParagraph = 0;
+
+ var commonEndSeq = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Take(countCommonAtEnd)
+ .Reverse()
+ .ToList();
+ var firstOfCommonEndSeq = commonEndSeq.First();
+ if (firstOfCommonEndSeq is ComparisonUnitWord)
+ {
+ // are there any paragraph marks in the common seq at end?
+ //if (commonEndSeq.Any(cu => cu.Contents.OfType<ComparisonUnitAtom>().First().ContentElement.Name == W.pPr))
+ if (commonEndSeq.Any(cu =>
+ {
+ var firstComparisonUnitAtom = cu.Contents.OfType<ComparisonUnitAtom>().FirstOrDefault();
+ if (firstComparisonUnitAtom == null)
+ return false;
+ return firstComparisonUnitAtom.ContentElement.Name == W.pPr;
+ }))
+ {
+ remainingInLeftParagraph = unknown
+ .ComparisonUnitArray1
+ .Reverse()
+ .Skip(countCommonAtEnd)
+ .TakeWhile(cu =>
+ {
+ if (!(cu is ComparisonUnitWord))
+ return false;
+ var firstComparisonUnitAtom = cu.Contents.OfType<ComparisonUnitAtom>().FirstOrDefault();
+ if (firstComparisonUnitAtom == null)
+ return true;
+ return firstComparisonUnitAtom.ContentElement.Name != W.pPr;
+ })
+ .Count();
+ remainingInRightParagraph = unknown
+ .ComparisonUnitArray2
+ .Reverse()
+ .Skip(countCommonAtEnd)
+ .TakeWhile(cu =>
+ {
+ if (!(cu is ComparisonUnitWord))
+ return false;
+ var firstComparisonUnitAtom = cu.Contents.OfType<ComparisonUnitAtom>().FirstOrDefault();
+ if (firstComparisonUnitAtom == null)
+ return true;
+ return firstComparisonUnitAtom.ContentElement.Name != W.pPr;
+ })
+ .Count();
+ }
+ }
+
+ var newSequence = new List<CorrelatedSequence>();
+
+ int beforeCommonParagraphLeft = unknown.ComparisonUnitArray1.Length - remainingInLeftParagraph - countCommonAtEnd;
+ int beforeCommonParagraphRight = unknown.ComparisonUnitArray2.Length - remainingInRightParagraph - countCommonAtEnd;
+
+ if (beforeCommonParagraphLeft != 0 && beforeCommonParagraphRight == 0)
+ {
+ CorrelatedSequence csDeleted = new CorrelatedSequence();
+ csDeleted.CorrelationStatus = CorrelationStatus.Deleted;
+ csDeleted.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Take(beforeCommonParagraphLeft).ToArray();
+ csDeleted.ComparisonUnitArray2 = null;
+ newSequence.Add(csDeleted);
+ }
+ else if (beforeCommonParagraphLeft == 0 && beforeCommonParagraphRight != 0)
+ {
+ CorrelatedSequence csInserted = new CorrelatedSequence();
+ csInserted.CorrelationStatus = CorrelationStatus.Inserted;
+ csInserted.ComparisonUnitArray1 = null;
+ csInserted.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Take(beforeCommonParagraphRight).ToArray();
+ newSequence.Add(csInserted);
+ }
+ else if (beforeCommonParagraphLeft != 0 && beforeCommonParagraphRight != 0)
+ {
+ CorrelatedSequence csUnknown = new CorrelatedSequence();
+ csUnknown.CorrelationStatus = CorrelationStatus.Unknown;
+ csUnknown.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Take(beforeCommonParagraphLeft).ToArray();
+ csUnknown.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Take(beforeCommonParagraphRight).ToArray();
+ newSequence.Add(csUnknown);
+ }
+ else if (beforeCommonParagraphLeft == 0 && beforeCommonParagraphRight == 0)
+ {
+ // nothing to do
+ }
+
+ if (remainingInLeftParagraph != 0 && remainingInRightParagraph == 0)
+ {
+ CorrelatedSequence csDeleted = new CorrelatedSequence();
+ csDeleted.CorrelationStatus = CorrelationStatus.Deleted;
+ csDeleted.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Skip(beforeCommonParagraphLeft).Take(remainingInLeftParagraph).ToArray();
+ csDeleted.ComparisonUnitArray2 = null;
+ newSequence.Add(csDeleted);
+ }
+ else if (remainingInLeftParagraph == 0 && remainingInRightParagraph != 0)
+ {
+ CorrelatedSequence csInserted = new CorrelatedSequence();
+ csInserted.CorrelationStatus = CorrelationStatus.Inserted;
+ csInserted.ComparisonUnitArray1 = null;
+ csInserted.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Skip(beforeCommonParagraphRight).Take(remainingInRightParagraph).ToArray();
+ newSequence.Add(csInserted);
+ }
+ else if (remainingInLeftParagraph != 0 && remainingInRightParagraph != 0)
+ {
+ CorrelatedSequence csUnknown = new CorrelatedSequence();
+ csUnknown.CorrelationStatus = CorrelationStatus.Unknown;
+ csUnknown.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Skip(beforeCommonParagraphLeft).Take(remainingInLeftParagraph).ToArray();
+ csUnknown.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Skip(beforeCommonParagraphRight).Take(remainingInRightParagraph).ToArray();
+ newSequence.Add(csUnknown);
+ }
+ else if (remainingInLeftParagraph == 0 && remainingInRightParagraph == 0)
+ {
+ // nothing to do
+ }
+
+ CorrelatedSequence csEqual = new CorrelatedSequence();
+ csEqual.CorrelationStatus = CorrelationStatus.Equal;
+ csEqual.ComparisonUnitArray1 = unknown.ComparisonUnitArray1.Skip(unknown.ComparisonUnitArray1.Length - countCommonAtEnd).ToArray();
+ csEqual.ComparisonUnitArray2 = unknown.ComparisonUnitArray2.Skip(unknown.ComparisonUnitArray2.Length - countCommonAtEnd).ToArray();
+ newSequence.Add(csEqual);
+
+ return newSequence;
+ }
+
+ return null;
+#if false
+ var middleLeft = unknown
+ .ComparisonUnitArray1
+ .Skip(countCommonAtBeginning)
+ .SkipLast(remainingInLeftParagraph)
+ .SkipLast(countCommonAtEnd)
+ .ToArray();
+
+ var middleRight = unknown
+ .ComparisonUnitArray2
+ .Skip(countCommonAtBeginning)
+ .SkipLast(remainingInRightParagraph)
+ .SkipLast(countCommonAtEnd)
+ .ToArray();
+
+ if (middleLeft.Length > 0 && middleRight.Length == 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Deleted;
+ cs.ComparisonUnitArray1 = middleLeft;
+ cs.ComparisonUnitArray2 = null;
+ newSequence.Add(cs);
+ }
+ else if (middleLeft.Length == 0 && middleRight.Length > 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Inserted;
+ cs.ComparisonUnitArray1 = null;
+ cs.ComparisonUnitArray2 = middleRight;
+ newSequence.Add(cs);
+ }
+ else if (middleLeft.Length > 0 && middleRight.Length > 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Unknown;
+ cs.ComparisonUnitArray1 = middleLeft;
+ cs.ComparisonUnitArray2 = middleRight;
+ newSequence.Add(cs);
+ }
+
+ var remainingInParaLeft = unknown
+ .ComparisonUnitArray1
+ .Skip(countCommonAtBeginning)
+ .Skip(middleLeft.Length)
+ .Take(remainingInLeftParagraph)
+ .ToArray();
+
+ var remainingInParaRight = unknown
+ .ComparisonUnitArray2
+ .Skip(countCommonAtBeginning)
+ .Skip(middleRight.Length)
+ .Take(remainingInRightParagraph)
+ .ToArray();
+
+ if (remainingInParaLeft.Length > 0 && remainingInParaRight.Length == 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Deleted;
+ cs.ComparisonUnitArray1 = remainingInParaLeft;
+ cs.ComparisonUnitArray2 = null;
+ newSequence.Add(cs);
+ }
+ else if (remainingInParaLeft.Length == 0 && remainingInParaRight.Length > 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Inserted;
+ cs.ComparisonUnitArray1 = null;
+ cs.ComparisonUnitArray2 = remainingInParaRight;
+ newSequence.Add(cs);
+ }
+ else if (remainingInParaLeft.Length > 0 && remainingInParaRight.Length > 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Unknown;
+ cs.ComparisonUnitArray1 = remainingInParaLeft;
+ cs.ComparisonUnitArray2 = remainingInParaRight;
+ newSequence.Add(cs);
+ }
+
+ if (countCommonAtEnd != 0)
+ {
+ CorrelatedSequence cs = new CorrelatedSequence();
+ cs.CorrelationStatus = CorrelationStatus.Equal;
+
+ cs.ComparisonUnitArray1 = unknown
+ .ComparisonUnitArray1
+ .Skip(countCommonAtBeginning + middleLeft.Length + remainingInParaLeft.Length)
+ .ToArray();
+
+ cs.ComparisonUnitArray2 = unknown
+ .ComparisonUnitArray2
+ .Skip(countCommonAtBeginning + middleRight.Length + remainingInParaRight.Length)
+ .ToArray();
+
+ if (cs.ComparisonUnitArray1.Length != cs.ComparisonUnitArray2.Length)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ newSequence.Add(cs);
+ }
+ return newSequence;
+#endif
+ }
+
+ private static List<ComparisonUnit[]> SplitAtParagraphMark(ComparisonUnit[] cua)
+ {
+ int i;
+ for (i = 0; i < cua.Length; i++)
+ {
+ var atom = cua[i].DescendantContentAtoms().FirstOrDefault();
+ if (atom != null && atom.ContentElement.Name == W.pPr)
+ break;
+ }
+ if (i == cua.Length)
+ {
+ return new List<ComparisonUnit[]>()
+ {
+ cua
+ };
+ }
+ return new List<ComparisonUnit[]>()
+ {
+ cua.Take(i).ToArray(),
+ cua.Skip(i).ToArray(),
+ };
+ }
+
+ private static void MoveLastSectPrToChildOfBody(XDocument newXDoc)
+ {
+ var lastParaWithSectPr = newXDoc
+ .Root
+ .Elements(W.body)
+ .Elements(W.p)
+ .Where(p => p.Elements(W.pPr).Elements(W.sectPr).Any())
+ .LastOrDefault();
+ if (lastParaWithSectPr != null)
+ {
+ newXDoc.Root.Element(W.body).Add(lastParaWithSectPr.Elements(W.pPr).Elements(W.sectPr));
+ lastParaWithSectPr.Elements(W.pPr).Elements(W.sectPr).Remove();
+ }
+ }
+
+ private static int s_MaxId = 0;
+
+ private static object ProduceNewWmlMarkupFromCorrelatedSequence(OpenXmlPart part,
+ IEnumerable<ComparisonUnitAtom> comparisonUnitAtomList,
+ WmlComparerSettings settings)
+ {
+ // fabricate new MainDocumentPart from correlatedSequence
+ s_MaxId = 0;
+ var newBodyChildren = CoalesceRecurse(part, comparisonUnitAtomList, 0, settings);
+ return newBodyChildren;
+ }
+
+ private static void FixUpDocPrIds(WordprocessingDocument wDoc)
+ {
+ var elementToFind = WP.docPr;
+ var docPrToChange = wDoc
+ .ContentParts()
+ .Select(cp => cp.GetXDocument())
+ .Select(xd => xd.Descendants().Where(d => d.Name == elementToFind))
+ .SelectMany(m => m);
+ var nextId = 1;
+ foreach (var item in docPrToChange)
+ {
+ var idAtt = item.Attribute("id");
+ if (idAtt != null)
+ idAtt.Value = (nextId++).ToString();
+ }
+ foreach (var cp in wDoc.ContentParts())
+ cp.PutXDocument();
+ }
+
+ private static void FixUpRevMarkIds(WordprocessingDocument wDoc)
+ {
+ var revMarksToChange = wDoc
+ .ContentParts()
+ .Select(cp => cp.GetXDocument())
+ .Select(xd => xd.Descendants().Where(d => d.Name == W.ins || d.Name == W.del))
+ .SelectMany(m => m);
+ var nextId = 0;
+ foreach (var item in revMarksToChange)
+ {
+ var idAtt = item.Attribute(W.id);
+ if (idAtt != null)
+ idAtt.Value = (nextId++).ToString();
+ }
+ foreach (var cp in wDoc.ContentParts())
+ cp.PutXDocument();
+ }
+
+ private static void FixUpShapeIds(WordprocessingDocument wDoc)
+ {
+ var elementToFind = VML.shape;
+ var shapeIdsToChange = wDoc
+ .ContentParts()
+ .Select(cp => cp.GetXDocument())
+ .Select(xd => xd.Descendants().Where(d => d.Name == elementToFind))
+ .SelectMany(m => m);
+ var nextId = 1;
+ foreach (var item in shapeIdsToChange)
+ {
+ var thisId = nextId++;
+
+ var idAtt = item.Attribute("id");
+ if (idAtt != null)
+ idAtt.Value = thisId.ToString();
+
+ var oleObject = item.Parent.Element(O.OLEObject);
+ if (oleObject != null)
+ {
+ var shapeIdAtt = oleObject.Attribute("ShapeID");
+ if (shapeIdAtt != null)
+ shapeIdAtt.Value = thisId.ToString();
+ }
+ }
+ foreach (var cp in wDoc.ContentParts())
+ cp.PutXDocument();
+ }
+
+ private static void FixUpGroupIds(WordprocessingDocument wDoc)
+ {
+ var elementToFind = VML.group;
+ var groupIdsToChange = wDoc
+ .ContentParts()
+ .Select(cp => cp.GetXDocument())
+ .Select(xd => xd.Descendants().Where(d => d.Name == elementToFind))
+ .SelectMany(m => m);
+ var nextId = 1;
+ foreach (var item in groupIdsToChange)
+ {
+ var thisId = nextId++;
+
+ var idAtt = item.Attribute("id");
+ if (idAtt != null)
+ idAtt.Value = thisId.ToString();
+ }
+ foreach (var cp in wDoc.ContentParts())
+ cp.PutXDocument();
+ }
+
+ private static void FixUpShapeTypeIds(WordprocessingDocument wDoc)
+ {
+ var elementToFind = VML.shapetype;
+ var shapeTypeIdsToChange = wDoc
+ .ContentParts()
+ .Select(cp => cp.GetXDocument())
+ .Select(xd => xd.Descendants().Where(d => d.Name == elementToFind))
+ .SelectMany(m => m);
+ var nextId = 1;
+ foreach (var item in shapeTypeIdsToChange)
+ {
+ var thisId = nextId++;
+
+ var idAtt = item.Attribute("id");
+ if (idAtt != null)
+ idAtt.Value = thisId.ToString();
+
+ var shape = item.Parent.Element(VML.shape);
+ if (shape != null)
+ {
+ var typeAtt = shape.Attribute("type");
+ if (typeAtt != null)
+ typeAtt.Value = thisId.ToString();
+ }
+ }
+ foreach (var cp in wDoc.ContentParts())
+ cp.PutXDocument();
+ }
+
+ private static object CoalesceRecurse(OpenXmlPart part, IEnumerable<ComparisonUnitAtom> list, int level, WmlComparerSettings settings)
+ {
+ var grouped = list.GroupBy(ca =>
+ {
+ if (level >= ca.AncestorElements.Length)
+ return "";
+ return ca.AncestorUnids[level];
+ })
+ .Where(g => g.Key != "");
+
+ // if there are no deeper children, then we're done.
+ if (!grouped.Any())
+ return null;
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var group in grouped)
+ {
+ sb.AppendFormat("Group Key: {0}", group.Key);
+ sb.Append(Environment.NewLine);
+ foreach (var groupChildItem in group)
+ {
+ sb.Append(" ");
+ sb.Append(groupChildItem.ToString(0));
+ sb.Append(Environment.NewLine);
+ }
+ sb.Append(Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var elementList = grouped
+ .Select(g =>
+ {
+ var ancestorBeingConstructed = g.First().AncestorElements[level]; // these will all be the same, by definition
+
+ // need to group by corr stat
+ var groupedChildren = g
+ .GroupAdjacent(gc =>
+ {
+ var key = "";
+ if (level < (gc.AncestorElements.Length - 1))
+ {
+ key = gc.AncestorUnids[level + 1];
+ }
+ if (gc.AncestorElements.Skip(level).Any(ae => ae.Name == W.txbxContent))
+ key += "|" + CorrelationStatus.Equal.ToString();
+ else
+ key += "|" + gc.CorrelationStatus.ToString();
+ return key;
+ })
+ .ToList();
+
+ if (ancestorBeingConstructed.Name == W.p)
+ {
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var spl = gc.Key.Split('|');
+ if (spl[0] == "")
+ return (object)gc.Select(gcc =>
+ {
+ var dup = new XElement(gcc.ContentElement);
+ if (spl[1] == "Deleted")
+ dup.Add(new XAttribute(PtOpenXml.Status, "Deleted"));
+ else if (spl[1] == "Inserted")
+ dup.Add(new XAttribute(PtOpenXml.Status, "Inserted"));
+ return dup;
+ });
+ else
+ {
+ return CoalesceRecurse(part, gc, level + 1, settings);
+ }
+ })
+ .ToList();
+
+ var newPara = new XElement(W.p,
+ ancestorBeingConstructed.Attributes().Where(a => a.Name.Namespace != PtOpenXml.pt),
+ new XAttribute(PtOpenXml.Unid, g.Key),
+ newChildElements);
+
+ return newPara;
+ }
+
+ if (ancestorBeingConstructed.Name == W.r)
+ {
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var spl = gc.Key.Split('|');
+ if (spl[0] == "")
+ return (object)gc.Select(gcc =>
+ {
+ var dup = new XElement(gcc.ContentElement);
+ if (spl[1] == "Deleted")
+ dup.Add(new XAttribute(PtOpenXml.Status, "Deleted"));
+ else if (spl[1] == "Inserted")
+ dup.Add(new XAttribute(PtOpenXml.Status, "Inserted"));
+ return dup;
+ });
+ else
+ {
+ return CoalesceRecurse(part, gc, level + 1, settings);
+ }
+ })
+ .ToList();
+
+ XElement rPr = ancestorBeingConstructed.Element(W.rPr);
+ var newRun = new XElement(W.r,
+ ancestorBeingConstructed.Attributes().Where(a => a.Name.Namespace != PtOpenXml.pt),
+ rPr,
+ newChildElements);
+ return newRun;
+ }
+
+ if (ancestorBeingConstructed.Name == W.t)
+ {
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var textOfTextElement = gc.Select(gce => gce.ContentElement.Value).StringConcatenate();
+ var del = gc.First().CorrelationStatus == CorrelationStatus.Deleted;
+ var ins = gc.First().CorrelationStatus == CorrelationStatus.Inserted;
+ if (del)
+ return (object)(new XElement(W.delText,
+ new XAttribute(PtOpenXml.Status, "Deleted"),
+ GetXmlSpaceAttribute(textOfTextElement),
+ textOfTextElement));
+ else if (ins)
+ return (object)(new XElement(W.t,
+ new XAttribute(PtOpenXml.Status, "Inserted"),
+ GetXmlSpaceAttribute(textOfTextElement),
+ textOfTextElement));
+ else
+ return (object)(new XElement(W.t,
+ GetXmlSpaceAttribute(textOfTextElement),
+ textOfTextElement));
+ })
+ .ToList();
+ return newChildElements;
+ }
+
+ if (ancestorBeingConstructed.Name == W.drawing)
+ {
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var del = gc.First().CorrelationStatus == CorrelationStatus.Deleted;
+ var ins = gc.First().CorrelationStatus == CorrelationStatus.Inserted;
+ if (del)
+ {
+ return (object)gc.Select(gcc =>
+ {
+ var newDrawing = new XElement(gcc.ContentElement);
+ newDrawing.Add(new XAttribute(PtOpenXml.Status, "Deleted"));
+
+ var openXmlPartOfDeletedContent = gc.First().Part;
+ var openXmlPartInNewDocument = part;
+ return gc.Select(gce =>
+ {
+ Package packageOfDeletedContent = openXmlPartOfDeletedContent.OpenXmlPackage.Package;
+ Package packageOfNewContent = openXmlPartInNewDocument.OpenXmlPackage.Package;
+ PackagePart partInDeletedDocument = packageOfDeletedContent.GetPart(part.Uri);
+ PackagePart partInNewDocument = packageOfNewContent.GetPart(part.Uri);
+ return MoveRelatedPartsToDestination(partInDeletedDocument, partInNewDocument, newDrawing);
+ });
+ });
+ }
+ else if (ins)
+ {
+ return gc.Select(gcc =>
+ {
+ var newDrawing = new XElement(gcc.ContentElement);
+ newDrawing.Add(new XAttribute(PtOpenXml.Status, "Inserted"));
+
+ var openXmlPartOfInsertedContent = gc.First().Part;
+ var openXmlPartInNewDocument = part;
+ return gc.Select(gce =>
+ {
+ Package packageOfSourceContent = openXmlPartOfInsertedContent.OpenXmlPackage.Package;
+ Package packageOfNewContent = openXmlPartInNewDocument.OpenXmlPackage.Package;
+ PackagePart partInDeletedDocument = packageOfSourceContent.GetPart(part.Uri);
+ PackagePart partInNewDocument = packageOfNewContent.GetPart(part.Uri);
+ return MoveRelatedPartsToDestination(partInDeletedDocument, partInNewDocument, newDrawing);
+ });
+ });
+ }
+ else
+ {
+ return gc.Select(gcc =>
+ {
+ return gcc.ContentElement;
+ });
+ }
+ })
+ .ToList();
+ return newChildElements;
+ }
+
+ if (ancestorBeingConstructed.Name == M.oMath || ancestorBeingConstructed.Name == M.oMathPara)
+ {
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var del = gc.First().CorrelationStatus == CorrelationStatus.Deleted;
+ var ins = gc.First().CorrelationStatus == CorrelationStatus.Inserted;
+ if (del)
+ {
+ return gc.Select(gcc =>
+ {
+ return new XElement(W.del,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions),
+ gcc.ContentElement);
+ });
+ }
+ else if (ins)
+ {
+ return gc.Select(gcc =>
+ {
+ return new XElement(W.ins,
+ new XAttribute(W.author, settings.AuthorForRevisions),
+ new XAttribute(W.id, s_MaxId++),
+ new XAttribute(W.date, settings.DateTimeForRevisions),
+ gcc.ContentElement);
+ });
+ }
+ else
+ {
+ return gc.Select(gcc => gcc.ContentElement);
+ }
+ })
+ .ToList();
+ return newChildElements;
+ }
+
+ if (AllowableRunChildren.Contains(ancestorBeingConstructed.Name))
+ {
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var del = gc.First().CorrelationStatus == CorrelationStatus.Deleted;
+ var ins = gc.First().CorrelationStatus == CorrelationStatus.Inserted;
+ if (del)
+ {
+ return gc.Select(gcc =>
+ {
+ var dup = new XElement(ancestorBeingConstructed.Name,
+ ancestorBeingConstructed.Attributes().Where(a => a.Name.Namespace != PtOpenXml.pt),
+ new XAttribute(PtOpenXml.Status, "Deleted"));
+ return dup;
+ });
+ }
+ else if (ins)
+ {
+ return gc.Select(gcc =>
+ {
+ var dup = new XElement(ancestorBeingConstructed.Name,
+ ancestorBeingConstructed.Attributes().Where(a => a.Name.Namespace != PtOpenXml.pt),
+ new XAttribute(PtOpenXml.Status, "Inserted"));
+ return dup;
+ });
+ }
+ else
+ {
+ return gc.Select(gcc => gcc.ContentElement);
+ }
+ })
+ .ToList();
+ return newChildElements;
+ }
+
+ if (ancestorBeingConstructed.Name == W.tbl)
+ return ReconstructElement(part, g, ancestorBeingConstructed, W.tblPr, W.tblGrid, null, level, settings);
+ if (ancestorBeingConstructed.Name == W.tr)
+ return ReconstructElement(part, g, ancestorBeingConstructed, W.trPr, null, null, level, settings);
+ if (ancestorBeingConstructed.Name == W.tc)
+ return ReconstructElement(part, g, ancestorBeingConstructed, W.tcPr, null, null, level, settings);
+ if (ancestorBeingConstructed.Name == W.sdt)
+ return ReconstructElement(part, g, ancestorBeingConstructed, W.sdtPr, W.sdtEndPr, null, level, settings);
+ if (ancestorBeingConstructed.Name == W.pict)
+ return ReconstructElement(part, g, ancestorBeingConstructed, VML.shapetype, null, null, level, settings);
+ if (ancestorBeingConstructed.Name == VML.shape)
+ return ReconstructElement(part, g, ancestorBeingConstructed, W10.wrap, null, null, level, settings);
+ if (ancestorBeingConstructed.Name == W._object)
+ return ReconstructElement(part, g, ancestorBeingConstructed, VML.shapetype, VML.shape, O.OLEObject, level, settings);
+ if (ancestorBeingConstructed.Name == W.ruby)
+ return ReconstructElement(part, g, ancestorBeingConstructed, W.rubyPr, null, null, level, settings);
+ return (object)ReconstructElement(part, g, ancestorBeingConstructed, null, null, null, level, settings);
+ })
+ .ToList();
+ return elementList;
+ }
+
+ private static XElement MoveRelatedPartsToDestination(PackagePart partOfDeletedContent, PackagePart partInNewDocument,
+ XElement contentElement)
+ {
+ var elementsToUpdate = contentElement
+ .Descendants()
+ .Where(d => d.Attributes().Any(a => ComparisonUnitWord.s_RelationshipAttributeNames.Contains(a.Name)))
+ .ToList();
+ foreach (var element in elementsToUpdate)
+ {
+ var attributesToUpdate = element
+ .Attributes()
+ .Where(a => ComparisonUnitWord.s_RelationshipAttributeNames.Contains(a.Name))
+ .ToList();
+ foreach (var att in attributesToUpdate)
+ {
+ var rId = (string)att;
+
+ var relationshipForDeletedPart = partOfDeletedContent.GetRelationship(rId);
+ if (relationshipForDeletedPart == null)
+ throw new FileFormatException("Invalid document");
+
+ Uri targetUri = PackUriHelper
+ .ResolvePartUri(
+ new Uri(partOfDeletedContent.Uri.ToString(), UriKind.Relative),
+ relationshipForDeletedPart.TargetUri);
+
+ var relatedPackagePart = partOfDeletedContent.Package.GetPart(targetUri);
+ var uriSplit = relatedPackagePart.Uri.ToString().Split('/');
+ var last = uriSplit[uriSplit.Length - 1].Split('.');
+ string uriString = null;
+ if (last.Length == 2)
+ {
+ uriString = uriSplit.SkipLast(1).Select(p => p + "/").StringConcatenate() +
+ "P" + Guid.NewGuid().ToString().Replace("-", "") + "." + last[1];
+ }
+ else
+ {
+ uriString = uriSplit.SkipLast(1).Select(p => p + "/").StringConcatenate() +
+ "P" + Guid.NewGuid().ToString().Replace("-", "");
+ }
+ Uri uri = null;
+ if (relatedPackagePart.Uri.IsAbsoluteUri)
+ uri = new Uri(uriString, UriKind.Absolute);
+ else
+ uri = new Uri(uriString, UriKind.Relative);
+
+ var newPart = partInNewDocument.Package.CreatePart(uri, relatedPackagePart.ContentType);
+ using (var oldPartStream = relatedPackagePart.GetStream())
+ using (var newPartStream = newPart.GetStream())
+ FileUtils.CopyStream(oldPartStream, newPartStream);
+
+ var newRid = "R" + Guid.NewGuid().ToString().Replace("-", "");
+ partInNewDocument.CreateRelationship(newPart.Uri, TargetMode.Internal, relationshipForDeletedPart.RelationshipType, newRid);
+ att.Value = newRid;
+
+ if (newPart.ContentType.EndsWith("xml"))
+ {
+ XDocument newPartXDoc = null;
+ using (var stream = newPart.GetStream())
+ {
+ newPartXDoc = XDocument.Load(stream);
+ MoveRelatedPartsToDestination(relatedPackagePart, newPart, newPartXDoc.Root);
+ }
+ using (var stream = newPart.GetStream())
+ newPartXDoc.Save(stream);
+ }
+ }
+ }
+ return contentElement;
+ }
+
+ private static XAttribute GetXmlSpaceAttribute(string textOfTextElement)
+ {
+ if (char.IsWhiteSpace(textOfTextElement[0]) ||
+ char.IsWhiteSpace(textOfTextElement[textOfTextElement.Length - 1]))
+ return new XAttribute(XNamespace.Xml + "space", "preserve");
+ return null;
+ }
+
+ private static XElement ReconstructElement(OpenXmlPart part, IGrouping<string, ComparisonUnitAtom> g, XElement ancestorBeingConstructed, XName props1XName,
+ XName props2XName, XName props3XName, int level, WmlComparerSettings settings)
+ {
+ var newChildElements = CoalesceRecurse(part, g, level + 1, settings);
+
+ object props1 = null;
+ if (props1XName != null)
+ props1 = ancestorBeingConstructed.Elements(props1XName);
+
+ object props2 = null;
+ if (props2XName != null)
+ props2 = ancestorBeingConstructed.Elements(props2XName);
+
+ object props3 = null;
+ if (props3XName != null)
+ props3 = ancestorBeingConstructed.Elements(props3XName);
+
+ var reconstructedElement = new XElement(ancestorBeingConstructed.Name,
+ ancestorBeingConstructed.Attributes(),
+ props1, props2, props3, newChildElements);
+
+ return reconstructedElement;
+ }
+
+ private static List<CorrelatedSequence> Lcs(ComparisonUnit[] cu1, ComparisonUnit[] cu2, WmlComparerSettings settings)
+ {
+ // set up initial state - one CorrelatedSequence, UnKnown, contents == entire sequences (both)
+ CorrelatedSequence cs = new CorrelatedSequence()
+ {
+ CorrelationStatus = OpenXmlPowerTools.CorrelationStatus.Unknown,
+ ComparisonUnitArray1 = cu1,
+ ComparisonUnitArray2 = cu2,
+ };
+ List<CorrelatedSequence> csList = new List<CorrelatedSequence>()
+ {
+ cs
+ };
+
+ while (true)
+ {
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in csList)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var unknown = csList
+ .FirstOrDefault(z => z.CorrelationStatus == CorrelationStatus.Unknown);
+
+ if (unknown != null)
+ {
+ // if unknown consists of a single group of the same type in each side, then can set some Unids in the 'after' document.
+ // if the unknown is a pair of single tables, then can set table Unid.
+ // if the unknown is a pair of single rows, then can set table and rows Unids.
+ // if the unknown is a pair of single cells, then can set table, row, and cell Unids.
+ // if the unknown is a pair of paragraphs, then can set paragraph (and all ancestor) Unids.
+ SetAfterUnids(unknown);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ sb.Append(unknown.ToString());
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ List<CorrelatedSequence> newSequence = ProcessCorrelatedHashes(unknown, settings);
+ if (newSequence == null)
+ {
+ newSequence = FindCommonAtBeginningAndEnd(unknown, settings);
+ if (newSequence == null)
+ {
+ newSequence = DoLcsAlgorithm(unknown, settings);
+ }
+ }
+
+ var indexOfUnknown = csList.IndexOf(unknown);
+ csList.Remove(unknown);
+
+ newSequence.Reverse();
+ foreach (var item in newSequence)
+ csList.Insert(indexOfUnknown, item);
+
+ continue;
+ }
+
+ return csList;
+ }
+ }
+
+ private static void SetAfterUnids(CorrelatedSequence unknown)
+ {
+ if (unknown.ComparisonUnitArray1.Length == 1 && unknown.ComparisonUnitArray2.Length == 1)
+ {
+ var cua1 = unknown.ComparisonUnitArray1[0] as ComparisonUnitGroup;
+ var cua2 = unknown.ComparisonUnitArray2[0] as ComparisonUnitGroup;
+ if (cua1 != null &&
+ cua2 != null &&
+ cua1.ComparisonUnitGroupType == cua2.ComparisonUnitGroupType)
+ {
+ var groupType = cua1.ComparisonUnitGroupType;
+ var da1 = cua1.DescendantContentAtoms();
+ var da2 = cua2.DescendantContentAtoms();
+ XName takeThruName = null;
+ switch (groupType)
+ {
+ case ComparisonUnitGroupType.Paragraph:
+ takeThruName = W.p;
+ break;
+ case ComparisonUnitGroupType.Table:
+ takeThruName = W.tbl;
+ break;
+ case ComparisonUnitGroupType.Row:
+ takeThruName = W.tr;
+ break;
+ case ComparisonUnitGroupType.Cell:
+ takeThruName = W.tc;
+ break;
+ case ComparisonUnitGroupType.Textbox:
+ takeThruName = W.txbxContent;
+ break;
+ }
+ if (takeThruName == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var relevantAncestors = new List<XElement>();
+ foreach (var ae in da1.First().AncestorElements)
+ {
+ if (ae.Name != takeThruName)
+ {
+ relevantAncestors.Add(ae);
+ continue;
+ }
+ relevantAncestors.Add(ae);
+ break;
+ }
+ var unidList = relevantAncestors
+ .Select(a =>
+ {
+ var unid = (string)a.Attribute(PtOpenXml.Unid);
+ if (unid == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ return unid;
+ })
+ .ToArray();
+ foreach (var da in da2)
+ {
+ var ancestorsToSet = da.AncestorElements.Take(unidList.Length);
+ var zipped = ancestorsToSet.Zip(unidList, (a, u) =>
+ new
+ {
+ Ancestor = a,
+ Unid = u,
+ });
+
+ foreach (var z in zipped)
+ {
+ var unid = z.Ancestor.Attribute(PtOpenXml.Unid);
+
+ if (z.Ancestor.Name == W.footnotes || z.Ancestor.Name == W.endnotes)
+ continue;
+
+ if (unid == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ unid.Value = z.Unid;
+ }
+ }
+ }
+ }
+ }
+
+ private static List<CorrelatedSequence> ProcessCorrelatedHashes(CorrelatedSequence unknown, WmlComparerSettings settings)
+ {
+ // never attempt this optimization if there are less than 3 groups
+ var maxd = Math.Min(unknown.ComparisonUnitArray1.Length, unknown.ComparisonUnitArray2.Length);
+ if (maxd < 3)
+ return null;
+
+ var firstInCu1 = unknown.ComparisonUnitArray1.FirstOrDefault() as ComparisonUnitGroup;
+ var firstInCu2 = unknown.ComparisonUnitArray2.FirstOrDefault() as ComparisonUnitGroup;
+ if (firstInCu1 != null && firstInCu2 != null)
+ {
+ if ((firstInCu1.ComparisonUnitGroupType == ComparisonUnitGroupType.Paragraph ||
+ firstInCu1.ComparisonUnitGroupType == ComparisonUnitGroupType.Table ||
+ firstInCu1.ComparisonUnitGroupType == ComparisonUnitGroupType.Row) &&
+ (firstInCu2.ComparisonUnitGroupType == ComparisonUnitGroupType.Paragraph ||
+ firstInCu2.ComparisonUnitGroupType == ComparisonUnitGroupType.Table ||
+ firstInCu2.ComparisonUnitGroupType == ComparisonUnitGroupType.Row))
+ {
+ var groupType = firstInCu1.ComparisonUnitGroupType;
+
+ // Next want to do the lcs algorithm on this.
+ // potentially, we will find all paragraphs are correlated, but they may not be for two reasons-
+ // - if there were changes that were not tracked
+ // - if the anomolies in the change tracking cause there to be a mismatch in the number of paragraphs
+ // therefore we are going to do the whole LCS algorithm thing
+ // and at the end of the process, we set up the correlated sequence list where correlated paragraphs are together in their
+ // own unknown correlated sequence.
+
+ var cul1 = unknown.ComparisonUnitArray1;
+ var cul2 = unknown.ComparisonUnitArray2;
+ int currentLongestCommonSequenceLength = 0;
+ int currentLongestCommonSequenceAtomCount = 0;
+ int currentI1 = -1;
+ int currentI2 = -1;
+ for (int i1 = 0; i1 < cul1.Length; i1++)
+ {
+ for (int i2 = 0; i2 < cul2.Length; i2++)
+ {
+ var thisSequenceLength = 0;
+ var thisSequenceAtomCount = 0;
+ var thisI1 = i1;
+ var thisI2 = i2;
+ while (true)
+ {
+ var group1 = cul1[thisI1] as ComparisonUnitGroup;
+ var group2 = cul2[thisI2] as ComparisonUnitGroup;
+ bool match = group1 != null &&
+ group2 != null &&
+ group1.ComparisonUnitGroupType == group2.ComparisonUnitGroupType &&
+ group1.CorrelatedSHA1Hash != null &&
+ group2.CorrelatedSHA1Hash != null &&
+ group1.CorrelatedSHA1Hash == group2.CorrelatedSHA1Hash;
+
+ if (match)
+ {
+ thisSequenceAtomCount += cul1[thisI1].DescendantContentAtomsCount;
+ thisI1++;
+ thisI2++;
+ thisSequenceLength++;
+ if (thisI1 == cul1.Length || thisI2 == cul2.Length)
+ {
+ if (thisSequenceAtomCount > currentLongestCommonSequenceAtomCount)
+ {
+ currentLongestCommonSequenceLength = thisSequenceLength;
+ currentLongestCommonSequenceAtomCount = thisSequenceAtomCount;
+ currentI1 = i1;
+ currentI2 = i2;
+ }
+ break;
+ }
+ continue;
+ }
+ else
+ {
+ if (thisSequenceAtomCount > currentLongestCommonSequenceAtomCount)
+ {
+ currentLongestCommonSequenceLength = thisSequenceLength;
+ currentLongestCommonSequenceAtomCount = thisSequenceAtomCount;
+ currentI1 = i1;
+ currentI2 = i2;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // here we want to have some sort of threshold, and if the currentLongestCommonSequenceLength is not longer than the threshold, then don't do anything
+ bool doCorrelation = false;
+ if (currentLongestCommonSequenceLength == 1)
+ {
+ var numberOfAtoms1 = unknown.ComparisonUnitArray1[currentI1].DescendantContentAtoms().Count();
+ var numberOfAtoms2 = unknown.ComparisonUnitArray2[currentI2].DescendantContentAtoms().Count();
+ if (numberOfAtoms1 > 16 && numberOfAtoms2 > 16)
+ doCorrelation = true;
+ }
+ else if (currentLongestCommonSequenceLength > 1 && currentLongestCommonSequenceLength <= 3)
+ {
+ var numberOfAtoms1 = unknown.ComparisonUnitArray1.Skip(currentI1).Take(currentLongestCommonSequenceLength).Select(z => z.DescendantContentAtoms().Count()).Sum();
+ var numberOfAtoms2 = unknown.ComparisonUnitArray2.Skip(currentI2).Take(currentLongestCommonSequenceLength).Select(z => z.DescendantContentAtoms().Count()).Sum();
+ if (numberOfAtoms1 > 32 && numberOfAtoms2 > 32)
+ doCorrelation = true;
+ }
+ else if (currentLongestCommonSequenceLength > 3)
+ doCorrelation = true;
+ if (doCorrelation)
+ {
+ var newListOfCorrelatedSequence = new List<CorrelatedSequence>();
+
+ if (currentI1 > 0 && currentI2 == 0)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Take(currentI1)
+ .ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ else if (currentI1 == 0 && currentI2 > 0)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Take(currentI2)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ else if (currentI1 > 0 && currentI2 > 0)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Take(currentI1)
+ .ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Take(currentI2)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ }
+ else if (currentI1 == 0 && currentI2 == 0)
+ {
+ // nothing to do
+ }
+
+ for (int i = 0; i < currentLongestCommonSequenceLength; i++)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Skip(currentI1)
+ .Skip(i)
+ .Take(1)
+ .ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Skip(currentI2)
+ .Skip(i)
+ .Take(1)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ }
+
+ int endI1 = currentI1 + currentLongestCommonSequenceLength;
+ int endI2 = currentI2 + currentLongestCommonSequenceLength;
+
+ if (endI1 < cul1.Length && endI2 == cul2.Length)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Skip(endI1)
+ .ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ else if (endI1 == cul1.Length && endI2 < cul2.Length)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Skip(endI2)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ else if (endI1 < cul1.Length && endI2 < cul2.Length)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Skip(endI1)
+ .ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Skip(endI2)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ }
+ else if (endI1 == cul1.Length && endI2 == cul2.Length)
+ {
+ // nothing to do
+ }
+ return newListOfCorrelatedSequence;
+ }
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private static List<CorrelatedSequence> DoLcsAlgorithm(CorrelatedSequence unknown, WmlComparerSettings settings)
+ {
+ var newListOfCorrelatedSequence = new List<CorrelatedSequence>();
+
+ var cul1 = unknown.ComparisonUnitArray1;
+ var cul2 = unknown.ComparisonUnitArray2;
+
+ // first thing to do - if we have an unknown with zero length on left or right side, create appropriate
+ // this is a code optimization that enables easier processing of cases elsewhere.
+ if (cul1.Length > 0 && cul2.Length == 0)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = cul1;
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ return newListOfCorrelatedSequence;
+ }
+ else if (cul1.Length == 0 && cul2.Length > 0)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = cul2;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ return newListOfCorrelatedSequence;
+ }
+ else if (cul1.Length == 0 && cul2.Length == 0)
+ {
+ return newListOfCorrelatedSequence; // this will effectively remove the unknown with no data on either side from the current data model.
+ }
+
+ int currentLongestCommonSequenceLength = 0;
+ int currentI1 = -1;
+ int currentI2 = -1;
+ for (int i1 = 0; i1 < cul1.Length - currentLongestCommonSequenceLength; i1++)
+ {
+ for (int i2 = 0; i2 < cul2.Length - currentLongestCommonSequenceLength; i2++)
+ {
+ var thisSequenceLength = 0;
+ var thisI1 = i1;
+ var thisI2 = i2;
+ while (true)
+ {
+ if (cul1[thisI1].SHA1Hash == cul2[thisI2].SHA1Hash)
+ {
+ thisI1++;
+ thisI2++;
+ thisSequenceLength++;
+ if (thisI1 == cul1.Length || thisI2 == cul2.Length)
+ {
+ if (thisSequenceLength > currentLongestCommonSequenceLength)
+ {
+ currentLongestCommonSequenceLength = thisSequenceLength;
+ currentI1 = i1;
+ currentI2 = i2;
+ }
+ break;
+ }
+ continue;
+ }
+ else
+ {
+ if (thisSequenceLength > currentLongestCommonSequenceLength)
+ {
+ currentLongestCommonSequenceLength = thisSequenceLength;
+ currentI1 = i1;
+ currentI2 = i2;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // never start a common section with a paragraph mark.
+ while (true)
+ {
+ if (currentLongestCommonSequenceLength <= 1)
+ break;
+
+ var firstCommon = cul1[currentI1];
+
+ var firstCommonWord = firstCommon as ComparisonUnitWord;
+ if (firstCommonWord == null)
+ break;
+
+ // if the word contains more than one atom, then not a paragraph mark
+ if (firstCommonWord.Contents.Count() != 1)
+ break;
+
+ var firstCommonAtom = firstCommonWord.Contents.First() as ComparisonUnitAtom;
+ if (firstCommonAtom == null)
+ break;
+
+ if (firstCommonAtom.ContentElement.Name != W.pPr)
+ break;
+
+ --currentLongestCommonSequenceLength;
+ if (currentLongestCommonSequenceLength == 0)
+ {
+ currentI1 = -1;
+ currentI2 = -1;
+ }
+ else
+ {
+ ++currentI1;
+ ++currentI2;
+ }
+ }
+
+ bool isOnlyParagraphMark = false;
+ if (currentLongestCommonSequenceLength == 1)
+ {
+ var firstCommon = cul1[currentI1];
+
+ var firstCommonWord = firstCommon as ComparisonUnitWord;
+ if (firstCommonWord != null)
+ {
+ // if the word contains more than one atom, then not a paragraph mark
+ if (firstCommonWord.Contents.Count() == 1)
+ {
+ var firstCommonAtom = firstCommonWord.Contents.First() as ComparisonUnitAtom;
+ if (firstCommonAtom != null)
+ {
+ if (firstCommonAtom.ContentElement.Name == W.pPr)
+ isOnlyParagraphMark = true;
+ }
+ }
+ }
+ }
+
+ // don't match just a single character
+ if (currentLongestCommonSequenceLength == 1)
+ {
+ var cuw2 = cul2[currentI2] as ComparisonUnitAtom;
+ if (cuw2 != null)
+ {
+ if (cuw2.ContentElement.Name == W.t && cuw2.ContentElement.Value == " ")
+ {
+ currentI1 = -1;
+ currentI2 = -1;
+ currentLongestCommonSequenceLength = 0;
+ }
+ }
+ }
+
+ // don't match only word break characters
+ if (currentLongestCommonSequenceLength > 0 && currentLongestCommonSequenceLength <= 3)
+ {
+ var commonSequence = cul1.Skip(currentI1).Take(currentLongestCommonSequenceLength).ToArray();
+ // if they are all ComparisonUnitWord objects
+ var oneIsNotWord = commonSequence.Any(cs => (cs as ComparisonUnitWord) == null);
+ var allAreWords = !oneIsNotWord;
+ if (allAreWords)
+ {
+ var contentOtherThanWordSplitChars = commonSequence
+ .Cast<ComparisonUnitWord>()
+ .Any(cs =>
+ {
+ var otherThanText = cs.DescendantContentAtoms().Any(dca => dca.ContentElement.Name != W.t);
+ if (otherThanText)
+ return true;
+ var otherThanWordSplit = cs
+ .DescendantContentAtoms()
+ .Any(dca =>
+ {
+ var charValue = dca.ContentElement.Value;
+ var isWordSplit = settings.WordSeparators.Contains(charValue[0]);
+ if (isWordSplit)
+ return false;
+ return true;
+ });
+ return otherThanWordSplit;
+ });
+ if (! contentOtherThanWordSplitChars)
+ {
+ currentI1 = -1;
+ currentI2 = -1;
+ currentLongestCommonSequenceLength = 0;
+ }
+ }
+ }
+
+ // if we are only looking at text, and if the longest common subsequence is less than 15% of the whole, then forget it,
+ // don't find that LCS.
+ if (!isOnlyParagraphMark && currentLongestCommonSequenceLength > 0)
+ {
+ var anyButWord1 = cul1.Any(cu => (cu as ComparisonUnitWord) == null);
+ var anyButWord2 = cul2.Any(cu => (cu as ComparisonUnitWord) == null);
+ if (!anyButWord1 && !anyButWord2)
+ {
+ var maxLen = Math.Max(cul1.Length, cul2.Length);
+ if (((double)currentLongestCommonSequenceLength / (double)maxLen) < settings.DetailThreshold)
+ {
+ currentI1 = -1;
+ currentI2 = -1;
+ currentLongestCommonSequenceLength = 0;
+ }
+ }
+ }
+
+ if (currentI1 == -1 && currentI2 == -1)
+ {
+ var leftLength = unknown.ComparisonUnitArray1.Length;
+ var leftTables = unknown.ComparisonUnitArray1.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Table).Count();
+ var leftRows = unknown.ComparisonUnitArray1.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Row).Count();
+ var leftCells = unknown.ComparisonUnitArray1.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Cell).Count();
+ var leftParagraphs = unknown.ComparisonUnitArray1.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Paragraph).Count();
+ var leftTextboxes = unknown.ComparisonUnitArray1.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Textbox).Count();
+ var leftWords = unknown.ComparisonUnitArray1.OfType<ComparisonUnitWord>().Count();
+
+ var rightLength = unknown.ComparisonUnitArray2.Length;
+ var rightTables = unknown.ComparisonUnitArray2.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Table).Count();
+ var rightRows = unknown.ComparisonUnitArray2.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Row).Count();
+ var rightCells = unknown.ComparisonUnitArray2.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Cell).Count();
+ var rightParagraphs = unknown.ComparisonUnitArray2.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Paragraph).Count();
+ var rightTextboxes = unknown.ComparisonUnitArray2.OfType<ComparisonUnitGroup>().Where(l => l.ComparisonUnitGroupType == ComparisonUnitGroupType.Textbox).Count();
+ var rightWords = unknown.ComparisonUnitArray2.OfType<ComparisonUnitWord>().Count();
+
+ // if either side has both words, rows and text boxes, then we need to separate out into separate unknown correlated sequences
+ // group adjacent based on whether word, row, or textbox
+ // in most cases, the count of groups will be the same, but they may differ
+ // if the first group on either side is word, then create a deleted or inserted corr sequ for it.
+ // then have counter on both sides pointing to the first matched pairs of rows
+ // create an unknown corr sequ for it.
+ // increment both counters
+ // if one is at end but the other is not, then tag the remaining content as inserted or deleted, and done.
+ // if both are at the end, then done
+ // return the new list of corr sequ
+
+ var leftOnlyWordsRowsTextboxes = leftLength == leftWords + leftRows + leftTextboxes;
+ var rightOnlyWordsRowsTextboxes = rightLength == rightWords + rightRows + rightTextboxes;
+ if ((leftWords > 0 || rightWords > 0) &&
+ (leftRows > 0 || rightRows > 0 || leftTextboxes > 0 || rightTextboxes > 0) &&
+ (leftOnlyWordsRowsTextboxes && rightOnlyWordsRowsTextboxes))
+ {
+ var leftGrouped = unknown
+ .ComparisonUnitArray1
+ .GroupAdjacent(cu =>
+ {
+ if (cu is ComparisonUnitWord)
+ {
+ return "Word";
+ }
+ else
+ {
+ var cug = cu as ComparisonUnitGroup;
+ if (cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Row)
+ return "Row";
+ if (cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Textbox)
+ return "Textbox";
+ throw new OpenXmlPowerToolsException("Internal error");
+ }
+ })
+ .ToArray();
+ var rightGrouped = unknown
+ .ComparisonUnitArray2
+ .GroupAdjacent(cu =>
+ {
+ if (cu is ComparisonUnitWord)
+ {
+ return "Word";
+ }
+ else
+ {
+ var cug = cu as ComparisonUnitGroup;
+ if (cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Row)
+ return "Row";
+ if (cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Textbox)
+ return "Textbox";
+ throw new OpenXmlPowerToolsException("Internal error");
+ }
+ })
+ .ToArray();
+ int iLeft = 0;
+ int iRight = 0;
+
+ // create an unknown corr sequ for it.
+ // increment both counters
+ // if one is at end but the other is not, then tag the remaining content as inserted or deleted, and done.
+ // if both are at the end, then done
+ // return the new list of corr sequ
+
+ while (true)
+ {
+ if (leftGrouped[iLeft].Key == rightGrouped[iRight].Key)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.ComparisonUnitArray1 = leftGrouped[iLeft].ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[iRight].ToArray();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ ++iLeft;
+ ++iRight;
+ }
+
+ // have to decide which of the following two branches to do first based on whether the left contains a paragraph mark
+ // i.e. cant insert a string of deleted text right before a table.
+
+ else if (leftGrouped[iLeft].Key == "Word" &&
+ leftGrouped[iLeft].Select(lg => lg.DescendantContentAtoms()).SelectMany(m => m).Last().ContentElement.Name != W.pPr &&
+ rightGrouped[iRight].Key == "Row")
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[iRight].ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ ++iRight;
+ }
+ else if (rightGrouped[iRight].Key == "Word" &&
+ rightGrouped[iRight].Select(lg => lg.DescendantContentAtoms()).SelectMany(m => m).Last().ContentElement.Name != W.pPr &&
+ leftGrouped[iLeft].Key == "Row")
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = leftGrouped[iLeft].ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ ++iLeft;
+ }
+
+ else if (leftGrouped[iLeft].Key == "Word" && rightGrouped[iRight].Key != "Word")
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.ComparisonUnitArray1 = leftGrouped[iLeft].ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ ++iLeft;
+ }
+
+ else if (leftGrouped[iLeft].Key != "Word" && rightGrouped[iRight].Key == "Word")
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[iRight].ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ ++iRight;
+ }
+
+ if (iLeft == leftGrouped.Length && iRight == rightGrouped.Length)
+ return newListOfCorrelatedSequence;
+
+ // if there is content on the left, but not content on the right
+ if (iRight == rightGrouped.Length)
+ {
+ for (int j = iLeft; j < leftGrouped.Length; j++)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.ComparisonUnitArray1 = leftGrouped[j].ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ return newListOfCorrelatedSequence;
+ }
+ // there is content on the right but not on the left
+ else if (iLeft == leftGrouped.Length)
+ {
+ for (int j = iRight; j < rightGrouped.Length; j++)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[j].ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ return newListOfCorrelatedSequence;
+ }
+ // else continue on next round.
+ }
+ }
+
+ // if both sides contain tables and paragraphs, then split into multiple unknown corr sequ
+ if (leftTables > 0 && rightTables > 0 &&
+ leftParagraphs > 0 && rightParagraphs > 0 &&
+ (leftLength > 1 || rightLength > 1))
+ {
+ var leftGrouped = unknown
+ .ComparisonUnitArray1
+ .GroupAdjacent(cu =>
+ {
+ var cug = cu as ComparisonUnitGroup;
+ if (cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Table)
+ return "Table";
+ else
+ return "Para";
+ })
+ .ToArray();
+ var rightGrouped = unknown
+ .ComparisonUnitArray2
+ .GroupAdjacent(cu =>
+ {
+ var cug = cu as ComparisonUnitGroup;
+ if (cug.ComparisonUnitGroupType == ComparisonUnitGroupType.Table)
+ return "Table";
+ else
+ return "Para";
+ })
+ .ToArray();
+ int iLeft = 0;
+ int iRight = 0;
+
+ // create an unknown corr sequ for it.
+ // increment both counters
+ // if one is at end but the other is not, then tag the remaining content as inserted or deleted, and done.
+ // if both are at the end, then done
+ // return the new list of corr sequ
+
+ while (true)
+ {
+ if ((leftGrouped[iLeft].Key == "Table" && rightGrouped[iRight].Key == "Table") ||
+ (leftGrouped[iLeft].Key == "Para" && rightGrouped[iRight].Key == "Para"))
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.ComparisonUnitArray1 = leftGrouped[iLeft].ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[iRight].ToArray();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ ++iLeft;
+ ++iRight;
+ }
+ else if (leftGrouped[iLeft].Key == "Para" && rightGrouped[iRight].Key == "Table")
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.ComparisonUnitArray1 = leftGrouped[iLeft].ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ ++iLeft;
+ }
+ else if (leftGrouped[iLeft].Key == "Table" && rightGrouped[iRight].Key == "Para")
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[iRight].ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ ++iRight;
+ }
+
+ if (iLeft == leftGrouped.Length && iRight == rightGrouped.Length)
+ return newListOfCorrelatedSequence;
+
+ // if there is content on the left, but not content on the right
+ if (iRight == rightGrouped.Length)
+ {
+ for (int j = iLeft; j < leftGrouped.Length; j++)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.ComparisonUnitArray1 = leftGrouped[j].ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ return newListOfCorrelatedSequence;
+ }
+ // there is content on the right but not on the left
+ else if (iLeft == leftGrouped.Length)
+ {
+ for (int j = iRight; j < rightGrouped.Length; j++)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = rightGrouped[j].ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ return newListOfCorrelatedSequence;
+ }
+ // else continue on next round.
+ }
+ }
+
+ // If both sides consists of a single table, and if the table contains merged cells, then mark as deleted/inserted
+ if (leftTables == 1 && leftLength == 1 &&
+ rightTables == 1 && rightLength == 1)
+ {
+ var result = DoLcsAlgorithmForTable(unknown, settings);
+ if (result != null)
+ return result;
+ }
+
+ // If either side contains only paras or tables, then flatten and iterate.
+ var leftOnlyParasTablesTextboxes = leftLength == leftTables + leftParagraphs + leftTextboxes;
+ var rightOnlyParasTablesTextboxes = rightLength == rightTables + rightParagraphs + rightTextboxes;
+ if (leftOnlyParasTablesTextboxes && rightOnlyParasTablesTextboxes)
+ {
+ // flatten paras and tables, and iterate
+ var left = unknown
+ .ComparisonUnitArray1
+ .Select(cu => cu.Contents)
+ .SelectMany(m => m)
+ .ToArray();
+
+ var right = unknown
+ .ComparisonUnitArray2
+ .Select(cu => cu.Contents)
+ .SelectMany(m => m)
+ .ToArray();
+
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = left;
+ unknownCorrelatedSequence.ComparisonUnitArray2 = right;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+
+ return newListOfCorrelatedSequence;
+ }
+
+ // if first of left is a row and first of right is a row
+ // then flatten the row to cells and iterate.
+
+ var firstLeft = unknown
+ .ComparisonUnitArray1
+ .FirstOrDefault() as ComparisonUnitGroup;
+
+ var firstRight = unknown
+ .ComparisonUnitArray2
+ .FirstOrDefault() as ComparisonUnitGroup;
+
+ if (firstLeft != null && firstRight != null)
+ {
+ if (firstLeft.ComparisonUnitGroupType == ComparisonUnitGroupType.Row &&
+ firstRight.ComparisonUnitGroupType == ComparisonUnitGroupType.Row)
+ {
+ ComparisonUnit[] leftContent = firstLeft.Contents.ToArray();
+ ComparisonUnit[] rightContent = firstRight.Contents.ToArray();
+
+ var lenLeft = leftContent.Length;
+ var lenRight = rightContent.Length;
+
+ if (lenLeft < lenRight)
+ leftContent = leftContent.Concat(Enumerable.Repeat<ComparisonUnit>(null, lenRight - lenLeft)).ToArray();
+ else if (lenRight < lenLeft)
+ rightContent = rightContent.Concat(Enumerable.Repeat<ComparisonUnit>(null, lenLeft - lenRight)).ToArray();
+
+ List<CorrelatedSequence> newCs = leftContent.Zip(rightContent, (l, r) =>
+ {
+ if (l != null && r != null)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.ComparisonUnitArray1 = new[] { l };
+ unknownCorrelatedSequence.ComparisonUnitArray2 = new[] { r };
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ return new[] { unknownCorrelatedSequence };
+ }
+ if (l == null)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = r.Contents.ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ return new[] { insertedCorrelatedSequence };
+ }
+ else if (r == null)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.ComparisonUnitArray1 = l.Contents.ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ return new[] { deletedCorrelatedSequence };
+ }
+ else
+ throw new OpenXmlPowerToolsException("Internal error");
+ })
+ .SelectMany(m => m)
+ .ToList();
+
+ foreach (var cs in newCs)
+ newListOfCorrelatedSequence.Add(cs);
+
+ var remainderLeft = unknown
+ .ComparisonUnitArray1
+ .Skip(1)
+ .ToArray();
+
+ var remainderRight = unknown
+ .ComparisonUnitArray2
+ .Skip(1)
+ .ToArray();
+
+ if (remainderLeft.Length > 0 && remainderRight.Length == 0)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = remainderLeft;
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ else if (remainderRight.Length > 0 && remainderLeft.Length == 0)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = remainderRight;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ else if (remainderLeft.Length > 0 && remainderRight.Length > 0)
+ {
+ var unknownCorrelatedSequence2 = new CorrelatedSequence();
+ unknownCorrelatedSequence2.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence2.ComparisonUnitArray1 = remainderLeft;
+ unknownCorrelatedSequence2.ComparisonUnitArray2 = remainderRight;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence2);
+ }
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in newListOfCorrelatedSequence)
+ sb.Append(item.ToString()).Append(Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ return newListOfCorrelatedSequence;
+ }
+ if (firstLeft.ComparisonUnitGroupType == ComparisonUnitGroupType.Cell &&
+ firstRight.ComparisonUnitGroupType == ComparisonUnitGroupType.Cell)
+ {
+ var left = firstLeft
+ .Contents
+ .ToArray();
+
+ var right = firstRight
+ .Contents
+ .ToArray();
+
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = left;
+ unknownCorrelatedSequence.ComparisonUnitArray2 = right;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+
+ var remainderLeft = unknown
+ .ComparisonUnitArray1
+ .Skip(1)
+ .ToArray();
+
+ var remainderRight = unknown
+ .ComparisonUnitArray2
+ .Skip(1)
+ .ToArray();
+
+ if (remainderLeft.Length > 0 && remainderRight.Length == 0)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = remainderLeft;
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ else if (remainderRight.Length > 0 && remainderLeft.Length == 0)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = remainderRight;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ else if (remainderLeft.Length > 0 && remainderRight.Length > 0)
+ {
+ var unknownCorrelatedSequence2 = new CorrelatedSequence();
+ unknownCorrelatedSequence2.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence2.ComparisonUnitArray1 = remainderLeft;
+ unknownCorrelatedSequence2.ComparisonUnitArray2 = remainderRight;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence2);
+ }
+
+ return newListOfCorrelatedSequence;
+ }
+ }
+
+ if (unknown.ComparisonUnitArray1.Any() && unknown.ComparisonUnitArray2.Any())
+ {
+ var left = unknown.ComparisonUnitArray1.First() as ComparisonUnitWord;
+ var right = unknown.ComparisonUnitArray2.First() as ComparisonUnitGroup;
+ if (left != null &&
+ right != null &&
+ right.ComparisonUnitGroupType == ComparisonUnitGroupType.Row)
+ {
+ var insertedCorrelatedSequence3 = new CorrelatedSequence();
+ insertedCorrelatedSequence3.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence3.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence3.ComparisonUnitArray2 = unknown.ComparisonUnitArray2;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence3);
+
+ var deletedCorrelatedSequence3 = new CorrelatedSequence();
+ deletedCorrelatedSequence3.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence3.ComparisonUnitArray1 = unknown.ComparisonUnitArray1;
+ deletedCorrelatedSequence3.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence3);
+
+ return newListOfCorrelatedSequence;
+ }
+
+ var left2 = unknown.ComparisonUnitArray1.First() as ComparisonUnitGroup;
+ var right2 = unknown.ComparisonUnitArray2.First() as ComparisonUnitWord;
+ if (right2 != null &&
+ left2 != null &&
+ left2.ComparisonUnitGroupType == ComparisonUnitGroupType.Row)
+ {
+ var deletedCorrelatedSequence3 = new CorrelatedSequence();
+ deletedCorrelatedSequence3.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence3.ComparisonUnitArray1 = unknown.ComparisonUnitArray1;
+ deletedCorrelatedSequence3.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence3);
+
+ var insertedCorrelatedSequence3 = new CorrelatedSequence();
+ insertedCorrelatedSequence3.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence3.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence3.ComparisonUnitArray2 = unknown.ComparisonUnitArray2;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence3);
+
+ return newListOfCorrelatedSequence;
+ }
+
+ var lastContentAtomLeft = unknown.ComparisonUnitArray1.Select(cu => cu.DescendantContentAtoms().Last()).LastOrDefault();
+ var lastContentAtomRight = unknown.ComparisonUnitArray2.Select(cu => cu.DescendantContentAtoms().Last()).LastOrDefault();
+ if (lastContentAtomLeft != null && lastContentAtomRight != null)
+ {
+ if (lastContentAtomLeft.ContentElement.Name == W.pPr &&
+ lastContentAtomRight.ContentElement.Name != W.pPr)
+ {
+ var insertedCorrelatedSequence5 = new CorrelatedSequence();
+ insertedCorrelatedSequence5.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence5.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence5.ComparisonUnitArray2 = unknown.ComparisonUnitArray2;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence5);
+
+ var deletedCorrelatedSequence5 = new CorrelatedSequence();
+ deletedCorrelatedSequence5.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence5.ComparisonUnitArray1 = unknown.ComparisonUnitArray1;
+ deletedCorrelatedSequence5.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence5);
+
+ return newListOfCorrelatedSequence;
+ }
+ else if (lastContentAtomLeft.ContentElement.Name != W.pPr &&
+ lastContentAtomRight.ContentElement.Name == W.pPr)
+ {
+ var deletedCorrelatedSequence5 = new CorrelatedSequence();
+ deletedCorrelatedSequence5.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence5.ComparisonUnitArray1 = unknown.ComparisonUnitArray1;
+ deletedCorrelatedSequence5.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence5);
+
+ var insertedCorrelatedSequence5 = new CorrelatedSequence();
+ insertedCorrelatedSequence5.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence5.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence5.ComparisonUnitArray2 = unknown.ComparisonUnitArray2;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence5);
+
+ return newListOfCorrelatedSequence;
+ }
+ }
+ }
+
+ var deletedCorrelatedSequence4 = new CorrelatedSequence();
+ deletedCorrelatedSequence4.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence4.ComparisonUnitArray1 = unknown.ComparisonUnitArray1;
+ deletedCorrelatedSequence4.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence4);
+
+ var insertedCorrelatedSequence4 = new CorrelatedSequence();
+ insertedCorrelatedSequence4.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence4.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence4.ComparisonUnitArray2 = unknown.ComparisonUnitArray2;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence4);
+
+ return newListOfCorrelatedSequence;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // here we have the longest common subsequence.
+ // but it may start in the middle of a paragraph.
+ // therefore need to dispose of the content from the beginning of the longest common subsequence to the beginning of the paragraph.
+ // this should be in a separate unknown region
+ // if countCommonAtEnd != 0, and if it contains a paragraph mark, then if there are comparison units in the same paragraph before the common at end (in either version)
+ // then we want to put all of those comparison units into a single unknown, where they must be resolved against each other. We don't want those
+ // comparison units to go into the middle unknown comparison unit.
+
+ int remainingInLeftParagraph = 0;
+ int remainingInRightParagraph = 0;
+ if (currentLongestCommonSequenceLength != 0)
+ {
+ var commonSeq = unknown
+ .ComparisonUnitArray1
+ .Skip(currentI1)
+ .Take(currentLongestCommonSequenceLength)
+ .ToList();
+ var firstOfCommonSeq = commonSeq.First();
+ if (firstOfCommonSeq is ComparisonUnitWord)
+ {
+ // are there any paragraph marks in the common seq at end?
+ if (commonSeq.Any(cu =>
+ {
+ var firstComparisonUnitAtom = cu.Contents.OfType<ComparisonUnitAtom>().FirstOrDefault();
+ if (firstComparisonUnitAtom == null)
+ return false;
+ return firstComparisonUnitAtom.ContentElement.Name == W.pPr;
+ }))
+ {
+ remainingInLeftParagraph = unknown
+ .ComparisonUnitArray1
+ .Take(currentI1)
+ .Reverse()
+ .TakeWhile(cu =>
+ {
+ if (!(cu is ComparisonUnitWord))
+ return false;
+ var firstComparisonUnitAtom = cu.Contents.OfType<ComparisonUnitAtom>().FirstOrDefault();
+ if (firstComparisonUnitAtom == null)
+ return true;
+ return firstComparisonUnitAtom.ContentElement.Name != W.pPr;
+ })
+ .Count();
+ remainingInRightParagraph = unknown
+ .ComparisonUnitArray2
+ .Take(currentI2)
+ .Reverse()
+ .TakeWhile(cu =>
+ {
+ if (!(cu is ComparisonUnitWord))
+ return false;
+ var firstComparisonUnitAtom = cu.Contents.OfType<ComparisonUnitAtom>().FirstOrDefault();
+ if (firstComparisonUnitAtom == null)
+ return true;
+ return firstComparisonUnitAtom.ContentElement.Name != W.pPr;
+ })
+ .Count();
+ }
+ }
+ }
+
+ var countBeforeCurrentParagraphLeft = currentI1 - remainingInLeftParagraph;
+ var countBeforeCurrentParagraphRight = currentI2 - remainingInRightParagraph;
+
+ if (countBeforeCurrentParagraphLeft > 0 && countBeforeCurrentParagraphRight == 0)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Take(countBeforeCurrentParagraphLeft)
+ .ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ else if (countBeforeCurrentParagraphLeft == 0 && countBeforeCurrentParagraphRight > 0)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Take(countBeforeCurrentParagraphRight)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ else if (countBeforeCurrentParagraphLeft > 0 && countBeforeCurrentParagraphRight > 0)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Take(countBeforeCurrentParagraphLeft)
+ .ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Take(countBeforeCurrentParagraphRight)
+ .ToArray();
+
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ }
+ else if (countBeforeCurrentParagraphLeft == 0 && countBeforeCurrentParagraphRight == 0)
+ {
+ // nothing to do
+ }
+
+ if (remainingInLeftParagraph > 0 && remainingInRightParagraph == 0)
+ {
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ deletedCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Skip(countBeforeCurrentParagraphLeft)
+ .Take(remainingInLeftParagraph)
+ .ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+ }
+ else if (remainingInLeftParagraph == 0 && remainingInRightParagraph > 0)
+ {
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Skip(countBeforeCurrentParagraphRight)
+ .Take(remainingInRightParagraph)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+ }
+ else if (remainingInLeftParagraph > 0 && remainingInRightParagraph > 0)
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence.ComparisonUnitArray1 = cul1
+ .Skip(countBeforeCurrentParagraphLeft)
+ .Take(remainingInLeftParagraph)
+ .ToArray();
+ unknownCorrelatedSequence.ComparisonUnitArray2 = cul2
+ .Skip(countBeforeCurrentParagraphRight)
+ .Take(remainingInRightParagraph)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence);
+ }
+ else if (remainingInLeftParagraph == 0 && remainingInRightParagraph == 0)
+ {
+ // nothing to do
+ }
+
+ var middleEqual = new CorrelatedSequence();
+ middleEqual.CorrelationStatus = CorrelationStatus.Equal;
+ middleEqual.ComparisonUnitArray1 = cul1
+ .Skip(currentI1)
+ .Take(currentLongestCommonSequenceLength)
+ .ToArray();
+ middleEqual.ComparisonUnitArray2 = cul2
+ .Skip(currentI2)
+ .Take(currentLongestCommonSequenceLength)
+ .ToArray();
+ newListOfCorrelatedSequence.Add(middleEqual);
+
+
+ int endI1 = currentI1 + currentLongestCommonSequenceLength;
+ int endI2 = currentI2 + currentLongestCommonSequenceLength;
+
+ var remaining1 = cul1
+ .Skip(endI1)
+ .ToArray();
+
+ var remaining2 = cul2
+ .Skip(endI2)
+ .ToArray();
+
+ // here is the point that we want to make a new unknown from this point to the end of the paragraph that contains the equal parts.
+ // this will never hurt anything, and will in many cases result in a better difference.
+
+ var leftCuw = middleEqual.ComparisonUnitArray1[middleEqual.ComparisonUnitArray1.Length - 1] as ComparisonUnitWord;
+ if (leftCuw != null)
+ {
+ var lastContentAtom = leftCuw.DescendantContentAtoms().LastOrDefault();
+ // if the middleEqual did not end with a paragraph mark
+ if (lastContentAtom != null && lastContentAtom.ContentElement.Name != W.pPr)
+ {
+ int idx1 = FindIndexOfNextParaMark(remaining1);
+ int idx2 = FindIndexOfNextParaMark(remaining2);
+
+ var unknownCorrelatedSequenceRemaining = new CorrelatedSequence();
+ unknownCorrelatedSequenceRemaining.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequenceRemaining.ComparisonUnitArray1 = remaining1.Take(idx1).ToArray();
+ unknownCorrelatedSequenceRemaining.ComparisonUnitArray2 = remaining2.Take(idx2).ToArray();
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequenceRemaining);
+
+ var unknownCorrelatedSequenceAfter = new CorrelatedSequence();
+ unknownCorrelatedSequenceAfter.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequenceAfter.ComparisonUnitArray1 = remaining1.Skip(idx1).ToArray();
+ unknownCorrelatedSequenceAfter.ComparisonUnitArray2 = remaining2.Skip(idx2).ToArray();
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequenceAfter);
+
+ return newListOfCorrelatedSequence;
+ }
+ }
+
+ var unknownCorrelatedSequence20 = new CorrelatedSequence();
+ unknownCorrelatedSequence20.CorrelationStatus = CorrelationStatus.Unknown;
+ unknownCorrelatedSequence20.ComparisonUnitArray1 = remaining1;
+ unknownCorrelatedSequence20.ComparisonUnitArray2 = remaining2;
+ newListOfCorrelatedSequence.Add(unknownCorrelatedSequence20);
+
+ return newListOfCorrelatedSequence;
+ }
+
+ private static int FindIndexOfNextParaMark(ComparisonUnit[] cul)
+ {
+ for (int i = 0; i < cul.Length; i++)
+ {
+ var cuw = cul[i] as ComparisonUnitWord;
+ var lastAtom = cuw.DescendantContentAtoms().LastOrDefault();
+ if (lastAtom.ContentElement.Name == W.pPr)
+ return i;
+ }
+ return cul.Length;
+ }
+
+ private static List<CorrelatedSequence> DoLcsAlgorithmForTable(CorrelatedSequence unknown, WmlComparerSettings settings)
+ {
+ List<CorrelatedSequence> newListOfCorrelatedSequence = new List<CorrelatedSequence>();
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // if we have a table with the same number of rows, and all rows have equal CorrelatedSHA1Hash, then we can flatten and compare every corresponding row.
+ // This is true regardless of whether there are horizontally or vertically merged cells, since that characteristic is incorporated into the CorrespondingSHA1Hash.
+ // This is probably not very common, but it will never do any harm.
+ var tblGroup1 = unknown.ComparisonUnitArray1.First() as ComparisonUnitGroup;
+ var tblGroup2 = unknown.ComparisonUnitArray2.First() as ComparisonUnitGroup;
+ if (tblGroup1.Contents.Count() == tblGroup2.Contents.Count()) // if there are the same number of rows
+ {
+ var zipped = tblGroup1.Contents.Zip(tblGroup2.Contents, (r1, r2) => new
+ {
+ Row1 = r1 as ComparisonUnitGroup,
+ Row2 = r2 as ComparisonUnitGroup,
+ });
+ var canCollapse = true;
+ if (zipped.Any(z => z.Row1.CorrelatedSHA1Hash != z.Row2.CorrelatedSHA1Hash))
+ canCollapse = false;
+ if (canCollapse)
+ {
+ newListOfCorrelatedSequence = zipped
+ .Select(z =>
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.ComparisonUnitArray1 = new[] { z.Row1 };
+ unknownCorrelatedSequence.ComparisonUnitArray2 = new[] { z.Row2 };
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ return unknownCorrelatedSequence;
+ })
+ .ToList();
+ return newListOfCorrelatedSequence;
+ }
+ }
+
+ var firstContentAtom1 = tblGroup1.DescendantContentAtoms().FirstOrDefault();
+ if (firstContentAtom1 == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var tblElement1 = firstContentAtom1
+ .AncestorElements
+ .Reverse()
+ .FirstOrDefault(a => a.Name == W.tbl);
+
+ var firstContentAtom2 = tblGroup2.DescendantContentAtoms().FirstOrDefault();
+ if (firstContentAtom2 == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+ var tblElement2 = firstContentAtom2
+ .AncestorElements
+ .Reverse()
+ .FirstOrDefault(a => a.Name == W.tbl);
+
+ var leftContainsMerged = tblElement1
+ .Descendants()
+ .Any(d => d.Name == W.vMerge || d.Name == W.gridSpan);
+
+ var rightContainsMerged = tblElement2
+ .Descendants()
+ .Any(d => d.Name == W.vMerge || d.Name == W.gridSpan);
+
+ if (leftContainsMerged || rightContainsMerged)
+ {
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ // If StructureSha1Hash is the same for both tables, then we know that the structure of the tables is identical, so we can break into correlated sequences for rows.
+ if (tblGroup1.StructureSHA1Hash != null &&
+ tblGroup2.StructureSHA1Hash != null &&
+ tblGroup1.StructureSHA1Hash == tblGroup2.StructureSHA1Hash)
+ {
+ var zipped = tblGroup1.Contents.Zip(tblGroup2.Contents, (r1, r2) => new
+ {
+ Row1 = r1 as ComparisonUnitGroup,
+ Row2 = r2 as ComparisonUnitGroup,
+ });
+ newListOfCorrelatedSequence = zipped
+ .Select(z =>
+ {
+ var unknownCorrelatedSequence = new CorrelatedSequence();
+ unknownCorrelatedSequence.ComparisonUnitArray1 = new[] { z.Row1 };
+ unknownCorrelatedSequence.ComparisonUnitArray2 = new[] { z.Row2 };
+ unknownCorrelatedSequence.CorrelationStatus = CorrelationStatus.Unknown;
+ return unknownCorrelatedSequence;
+ })
+ .ToList();
+ return newListOfCorrelatedSequence;
+ }
+
+ // otherwise flatten to rows
+ var deletedCorrelatedSequence = new CorrelatedSequence();
+ deletedCorrelatedSequence.ComparisonUnitArray1 = unknown
+ .ComparisonUnitArray1
+ .Select(z => z.Contents)
+ .SelectMany(m => m)
+ .ToArray();
+ deletedCorrelatedSequence.ComparisonUnitArray2 = null;
+ deletedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Deleted;
+ newListOfCorrelatedSequence.Add(deletedCorrelatedSequence);
+
+ var insertedCorrelatedSequence = new CorrelatedSequence();
+ insertedCorrelatedSequence.ComparisonUnitArray1 = null;
+ insertedCorrelatedSequence.ComparisonUnitArray2 = unknown
+ .ComparisonUnitArray2
+ .Select(z => z.Contents)
+ .SelectMany(m => m)
+ .ToArray();
+ insertedCorrelatedSequence.CorrelationStatus = CorrelationStatus.Inserted;
+ newListOfCorrelatedSequence.Add(insertedCorrelatedSequence);
+
+ return newListOfCorrelatedSequence;
+ }
+ return null;
+ }
+
+ private static XName[] WordBreakElements = new XName[] {
+ W.pPr,
+ W.tab,
+ W.br,
+ W.continuationSeparator,
+ W.cr,
+ W.dayLong,
+ W.dayShort,
+ W.drawing,
+ W.pict,
+ W.endnoteRef,
+ W.footnoteRef,
+ W.monthLong,
+ W.monthShort,
+ W.noBreakHyphen,
+ W._object,
+ W.ptab,
+ W.separator,
+ W.sym,
+ W.yearLong,
+ W.yearShort,
+ M.oMathPara,
+ M.oMath,
+ W.footnoteReference,
+ W.endnoteReference,
+ };
+
+ private class Atgbw
+ {
+ public int? Key;
+ public ComparisonUnitAtom ComparisonUnitAtomMember;
+ public int NextIndex;
+ }
+
+ private static ComparisonUnit[] GetComparisonUnitList(ComparisonUnitAtom[] comparisonUnitAtomList, WmlComparerSettings settings)
+ {
+ var seed = new Atgbw()
+ {
+ Key = null,
+ ComparisonUnitAtomMember = null,
+ NextIndex = 0,
+ };
+
+ var groupingKey = comparisonUnitAtomList
+ .Rollup(seed, (sr, prevAtgbw, i) =>
+ {
+ int? key = null;
+ var nextIndex = prevAtgbw.NextIndex;
+ if (sr.ContentElement.Name == W.t)
+ {
+ string chr = sr.ContentElement.Value;
+ var ch = chr[0];
+ if (ch == '.' || ch == ',')
+ {
+ bool beforeIsDigit = false;
+ if (i > 0)
+ {
+ var prev = comparisonUnitAtomList[i - 1];
+ if (prev.ContentElement.Name == W.t && char.IsDigit(prev.ContentElement.Value[0]))
+ beforeIsDigit = true;
+ }
+ bool afterIsDigit = false;
+ if (i < comparisonUnitAtomList.Length - 1)
+ {
+ var next = comparisonUnitAtomList[i + 1];
+ if (next.ContentElement.Name == W.t && char.IsDigit(next.ContentElement.Value[0]))
+ afterIsDigit = true;
+ }
+ if (beforeIsDigit || afterIsDigit)
+ {
+ key = nextIndex;
+ }
+ else
+ {
+ nextIndex++;
+ key = nextIndex;
+ nextIndex++;
+ }
+ }
+ else if (settings.WordSeparators.Contains(ch))
+ {
+ nextIndex++;
+ key = nextIndex;
+ nextIndex++;
+ }
+ else
+ {
+ key = nextIndex;
+ }
+ }
+ else if (WordBreakElements.Contains(sr.ContentElement.Name))
+ {
+ nextIndex++;
+ key = nextIndex;
+ nextIndex++;
+ }
+ else
+ {
+ key = nextIndex;
+ }
+ return new Atgbw()
+ {
+ Key = key,
+ ComparisonUnitAtomMember = sr,
+ NextIndex = nextIndex,
+ };
+ });
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in groupingKey)
+ {
+ sb.Append(item.Key + Environment.NewLine);
+ sb.Append(" " + item.ComparisonUnitAtomMember.ToString(0) + Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var groupedByWords = groupingKey
+ .GroupAdjacent(gc => gc.Key);
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var group in groupedByWords)
+ {
+ sb.Append("Group ===== " + group.Key + Environment.NewLine);
+ foreach (var gc in group)
+ {
+ sb.Append(" " + gc.ComparisonUnitAtomMember.ToString(0) + Environment.NewLine);
+ }
+ }
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var withHierarchicalGroupingKey = groupedByWords
+ .Select(g =>
+ {
+ var hierarchicalGroupingArray = g
+ .First()
+ .ComparisonUnitAtomMember
+ .AncestorElements
+ .Where(a => ComparisonGroupingElements.Contains(a.Name))
+ .Select(a => a.Name.LocalName + ":" + (string)a.Attribute(PtOpenXml.Unid))
+ .ToArray();
+
+ return new WithHierarchicalGroupingKey()
+ {
+ ComparisonUnitWord = new ComparisonUnitWord(g.Select(gc => gc.ComparisonUnitAtomMember)),
+ HierarchicalGroupingArray = hierarchicalGroupingArray,
+ };
+ }
+ )
+ .ToArray();
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var group in withHierarchicalGroupingKey)
+ {
+ sb.Append("Grouping Array: " + group.HierarchicalGroupingArray.Select(gam => gam + " - ").StringConcatenate() + Environment.NewLine);
+ foreach (var gc in group.ComparisonUnitWord.Contents)
+ {
+ sb.Append(" " + gc.ToString(0) + Environment.NewLine);
+ }
+ }
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ var cul = GetHierarchicalComparisonUnits(withHierarchicalGroupingKey, 0).ToArray();
+
+ if (s_False)
+ {
+ var str = ComparisonUnit.ComparisonUnitListToString(cul);
+ TestUtil.NotePad(str);
+ }
+
+ return cul;
+ }
+
+ private static IEnumerable<ComparisonUnit> GetHierarchicalComparisonUnits(IEnumerable<WithHierarchicalGroupingKey> input, int level)
+ {
+ var grouped = input
+ .GroupAdjacent(whgk =>
+ {
+ if (level >= whgk.HierarchicalGroupingArray.Length)
+ return "";
+ return whgk.HierarchicalGroupingArray[level];
+ });
+ var retList = grouped
+ .Select(gc =>
+ {
+ if (gc.Key == "")
+ {
+ return (IEnumerable<ComparisonUnit>)gc.Select(whgk => whgk.ComparisonUnitWord).ToList();
+ }
+ else
+ {
+ ComparisonUnitGroupType? group = null;
+ var spl = gc.Key.Split(':');
+ if (spl[0] == "p")
+ group = ComparisonUnitGroupType.Paragraph;
+ else if (spl[0] == "tbl")
+ group = ComparisonUnitGroupType.Table;
+ else if (spl[0] == "tr")
+ group = ComparisonUnitGroupType.Row;
+ else if (spl[0] == "tc")
+ group = ComparisonUnitGroupType.Cell;
+ else if (spl[0] == "txbxContent")
+ group = ComparisonUnitGroupType.Textbox;
+ var childHierarchicalComparisonUnits = GetHierarchicalComparisonUnits(gc, level + 1);
+ var newCompUnitGroup = new ComparisonUnitGroup(childHierarchicalComparisonUnits, (ComparisonUnitGroupType)group, level);
+ return new[] { newCompUnitGroup };
+ }
+ })
+ .SelectMany(m => m)
+ .ToList();
+ return retList;
+ }
+
+ private static XName[] AllowableRunChildren = new XName[] {
+ W.br,
+ W.drawing,
+ W.cr,
+ W.dayLong,
+ W.dayShort,
+ W.footnoteReference,
+ W.endnoteReference,
+ W.monthLong,
+ W.monthShort,
+ W.noBreakHyphen,
+ //W._object,
+ W.pgNum,
+ W.ptab,
+ W.softHyphen,
+ W.sym,
+ W.tab,
+ W.yearLong,
+ W.yearShort,
+ M.oMathPara,
+ M.oMath,
+ W.fldChar,
+ W.instrText,
+ };
+
+ private static XName[] ElementsToThrowAway = new XName[] {
+ W.bookmarkStart,
+ W.bookmarkEnd,
+ W.commentRangeStart,
+ W.commentRangeEnd,
+ W.lastRenderedPageBreak,
+ W.proofErr,
+ W.tblPr,
+ W.sectPr,
+ W.permEnd,
+ W.permStart,
+ W.footnoteRef,
+ W.endnoteRef,
+ W.separator,
+ W.continuationSeparator,
+ };
+
+ private static XName[] ElementsToHaveSha1Hash = new XName[]
+ {
+ W.p,
+ W.tbl,
+ W.tr,
+ W.tc,
+ W.drawing,
+ W.pict,
+ W.txbxContent,
+ };
+
+ private static XName[] InvalidElements = new XName[]
+ {
+ W.altChunk,
+ W.customXml,
+ W.customXmlDelRangeEnd,
+ W.customXmlDelRangeStart,
+ W.customXmlInsRangeEnd,
+ W.customXmlInsRangeStart,
+ W.customXmlMoveFromRangeEnd,
+ W.customXmlMoveFromRangeStart,
+ W.customXmlMoveToRangeEnd,
+ W.customXmlMoveToRangeStart,
+ W.moveFrom,
+ W.moveFromRangeStart,
+ W.moveFromRangeEnd,
+ W.moveTo,
+ W.moveToRangeStart,
+ W.moveToRangeEnd,
+ W.subDoc,
+ };
+
+ private class RecursionInfo
+ {
+ public XName ElementName;
+ public XName[] ChildElementPropertyNames;
+ }
+
+ private static RecursionInfo[] RecursionElements = new RecursionInfo[]
+ {
+ new RecursionInfo()
+ {
+ ElementName = W.del,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.ins,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.tbl,
+ ChildElementPropertyNames = new[] { W.tblPr, W.tblGrid, W.tblPrEx },
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.tr,
+ ChildElementPropertyNames = new[] { W.trPr, W.tblPrEx },
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.tc,
+ ChildElementPropertyNames = new[] { W.tcPr, W.tblPrEx },
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.pict,
+ ChildElementPropertyNames = new[] { VML.shapetype },
+ },
+ new RecursionInfo()
+ {
+ ElementName = VML.group,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = VML.shape,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = VML.rect,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = VML.textbox,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = O._lock,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.txbxContent,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W10.wrap,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.sdt,
+ ChildElementPropertyNames = new[] { W.sdtPr, W.sdtEndPr },
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.sdtContent,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.hyperlink,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.fldSimple,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = VML.shapetype,
+ ChildElementPropertyNames = null,
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.smartTag,
+ ChildElementPropertyNames = new[] { W.smartTagPr },
+ },
+ new RecursionInfo()
+ {
+ ElementName = W.ruby,
+ ChildElementPropertyNames = new[] { W.rubyPr },
+ },
+ };
+
+ internal static ComparisonUnitAtom[] CreateComparisonUnitAtomList(OpenXmlPart part, XElement contentParent, WmlComparerSettings settings)
+ {
+ VerifyNoInvalidContent(contentParent);
+ AssignUnidToAllElements(contentParent); // add the Guid id to every element
+ MoveLastSectPrIntoLastParagraph(contentParent);
+ var cal = CreateComparisonUnitAtomListInternal(part, contentParent, settings).ToArray();
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var item in cal)
+ sb.Append(item.ToString() + Environment.NewLine);
+ var sbs = sb.ToString();
+ TestUtil.NotePad(sbs);
+ }
+
+ return cal;
+ }
+
+ private static void VerifyNoInvalidContent(XElement contentParent)
+ {
+ var invalidElement = contentParent.Descendants().FirstOrDefault(d => InvalidElements.Contains(d.Name));
+ if (invalidElement == null)
+ return;
+ throw new NotSupportedException("Document contains " + invalidElement.Name.LocalName);
+ }
+
+ internal static XDocument Coalesce(ComparisonUnitAtom[] comparisonUnitAtomList)
+ {
+ XDocument newXDoc = new XDocument();
+ var newBodyChildren = CoalesceRecurse(comparisonUnitAtomList, 0);
+ newXDoc.Add(new XElement(W.document,
+ new XAttribute(XNamespace.Xmlns + "w", W.w.NamespaceName),
+ new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName),
+ new XElement(W.body, newBodyChildren)));
+
+ // little bit of cleanup
+ MoveLastSectPrToChildOfBody(newXDoc);
+ XElement newXDoc2Root = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(newXDoc.Root);
+ newXDoc.Root.ReplaceWith(newXDoc2Root);
+ return newXDoc;
+ }
+
+ private static object CoalesceRecurse(IEnumerable<ComparisonUnitAtom> list, int level)
+ {
+ var grouped = list
+ .GroupBy(sr =>
+ {
+ // per the algorithm, The following condition will never evaluate to true
+ // if it evaluates to true, then the basic mechanism for breaking a hierarchical structure into flat and back is broken.
+
+ // for a table, we initially get all ComparisonUnitAtoms for the entire table, then process. When processing a row,
+ // no ComparisonUnitAtoms will have ancestors outside the row. Ditto for cells, and on down the tree.
+ if (level >= sr.AncestorElements.Length)
+ throw new OpenXmlPowerToolsException("Internal error 4 - why do we have ComparisonUnitAtom objects with fewer ancestors than its siblings?");
+
+ var unid = (string)sr.AncestorElements[level].Attribute(PtOpenXml.Unid);
+ return unid;
+ });
+
+ if (s_False)
+ {
+ var sb = new StringBuilder();
+ foreach (var group in grouped)
+ {
+ sb.AppendFormat("Group Key: {0}", group.Key);
+ sb.Append(Environment.NewLine);
+ foreach (var groupChildItem in group)
+ {
+ sb.Append(" ");
+ sb.Append(groupChildItem.ToString(0));
+ sb.Append(Environment.NewLine);
+ }
+ sb.Append(Environment.NewLine);
+ }
+ var sbs = sb.ToString();
+ }
+
+ var elementList = grouped
+ .Select(g =>
+ {
+ // see the comment above at the beginning of CoalesceRecurse
+ if (level >= g.First().AncestorElements.Length)
+ throw new OpenXmlPowerToolsException("Internal error 3 - why do we have ComparisonUnitAtom objects with fewer ancestors than its siblings?");
+
+ var ancestorBeingConstructed = g.First().AncestorElements[level];
+
+ if (ancestorBeingConstructed.Name == W.p)
+ {
+ var groupedChildren = g
+ .GroupAdjacent(gc => gc.ContentElement.Name.ToString());
+ var newChildElements = groupedChildren
+ .Where(gc => gc.First().ContentElement.Name != W.pPr)
+ .Select(gc =>
+ {
+ return CoalesceRecurse(gc, level + 1);
+ });
+ var newParaProps = groupedChildren
+ .Where(gc => gc.First().ContentElement.Name == W.pPr)
+ .Select(gc => gc.Select(gce => gce.ContentElement));
+ return new XElement(W.p,
+ ancestorBeingConstructed.Attributes(),
+ newParaProps, newChildElements);
+ }
+
+ if (ancestorBeingConstructed.Name == W.r)
+ {
+ var groupedChildren = g
+ .GroupAdjacent(gc => gc.ContentElement.Name.ToString());
+ var newChildElements = groupedChildren
+ .Select(gc =>
+ {
+ var name = gc.First().ContentElement.Name;
+ if (name == W.t || name == W.delText)
+ {
+ var textOfTextElement = gc.Select(gce => gce.ContentElement.Value).StringConcatenate();
+ return (object)(new XElement(name,
+ GetXmlSpaceAttribute(textOfTextElement),
+ textOfTextElement));
+ }
+ else
+ return gc.Select(gce => gce.ContentElement);
+ });
+ var runProps = ancestorBeingConstructed.Elements(W.rPr);
+ return new XElement(W.r, runProps, newChildElements);
+ }
+
+ var re = RecursionElements.FirstOrDefault(z => z.ElementName == ancestorBeingConstructed.Name);
+ if (re != null)
+ {
+ return ReconstructElement(g, ancestorBeingConstructed, re.ChildElementPropertyNames, level);
+ }
+
+ var newElement = new XElement(ancestorBeingConstructed.Name,
+ ancestorBeingConstructed.Attributes(),
+ CoalesceRecurse(g, level + 1));
+
+ return newElement;
+ })
+ .ToList();
+ return elementList;
+ }
+
+ private static XElement ReconstructElement(IGrouping<string, ComparisonUnitAtom> g, XElement ancestorBeingConstructed, XName[] childPropElementNames, int level)
+ {
+ var newChildElements = CoalesceRecurse(g, level + 1);
+ IEnumerable<XElement> childProps = null;
+ if (childPropElementNames != null)
+ childProps = ancestorBeingConstructed.Elements()
+ .Where(a => childPropElementNames.Contains(a.Name));
+
+ var reconstructedElement = new XElement(ancestorBeingConstructed.Name, childProps, newChildElements);
+ return reconstructedElement;
+ }
+
+ private static void MoveLastSectPrIntoLastParagraph(XElement contentParent)
+ {
+ var lastSectPrList = contentParent.Elements(W.sectPr).ToList();
+ if (lastSectPrList.Count() > 1)
+ throw new OpenXmlPowerToolsException("Invalid document");
+ var lastSectPr = lastSectPrList.FirstOrDefault();
+ if (lastSectPr != null)
+ {
+ var lastParagraph = contentParent.Elements(W.p).LastOrDefault();
+ if (lastParagraph == null)
+ throw new OpenXmlPowerToolsException("Invalid document");
+ var pPr = lastParagraph.Element(W.pPr);
+ if (pPr == null)
+ {
+ pPr = new XElement(W.pPr);
+ lastParagraph.AddFirst(W.pPr);
+ }
+ pPr.Add(lastSectPr);
+ contentParent.Elements(W.sectPr).Remove();
+ }
+ }
+
+ private static List<ComparisonUnitAtom> CreateComparisonUnitAtomListInternal(OpenXmlPart part, XElement contentParent, WmlComparerSettings settings)
+ {
+ var comparisonUnitAtomList = new List<ComparisonUnitAtom>();
+ CreateComparisonUnitAtomListRecurse(part, contentParent, comparisonUnitAtomList, settings);
+ return comparisonUnitAtomList;
+ }
+
+ private static XName[] ComparisonGroupingElements = new[] {
+ W.p,
+ W.tbl,
+ W.tr,
+ W.tc,
+ W.txbxContent,
+ };
+
+ private static void CreateComparisonUnitAtomListRecurse(OpenXmlPart part, XElement element, List<ComparisonUnitAtom> comparisonUnitAtomList, WmlComparerSettings settings)
+ {
+ if (element.Name == W.body || element.Name == W.footnote || element.Name == W.endnote)
+ {
+ foreach (var item in element.Elements())
+ CreateComparisonUnitAtomListRecurse(part, item, comparisonUnitAtomList, settings);
+ return;
+ }
+
+ if (element.Name == W.p)
+ {
+ var paraChildrenToProcess = element
+ .Elements()
+ .Where(e => e.Name != W.pPr);
+ foreach (var item in paraChildrenToProcess)
+ CreateComparisonUnitAtomListRecurse(part, item, comparisonUnitAtomList, settings);
+ var paraProps = element.Element(W.pPr);
+ if (paraProps == null)
+ {
+ ComparisonUnitAtom pPrComparisonUnitAtom = new ComparisonUnitAtom(
+ new XElement(W.pPr),
+ element.AncestorsAndSelf().TakeWhile(a => a.Name != W.body && a.Name != W.footnotes && a.Name != W.endnotes).Reverse().ToArray(),
+ part,
+ settings);
+ comparisonUnitAtomList.Add(pPrComparisonUnitAtom);
+ }
+ else
+ {
+ ComparisonUnitAtom pPrComparisonUnitAtom = new ComparisonUnitAtom(
+ paraProps,
+ element.AncestorsAndSelf().TakeWhile(a => a.Name != W.body && a.Name != W.footnotes && a.Name != W.endnotes).Reverse().ToArray(),
+ part,
+ settings);
+ comparisonUnitAtomList.Add(pPrComparisonUnitAtom);
+ }
+ return;
+ }
+
+ if (element.Name == W.r)
+ {
+ var runChildrenToProcess = element
+ .Elements()
+ .Where(e => e.Name != W.rPr);
+ foreach (var item in runChildrenToProcess)
+ CreateComparisonUnitAtomListRecurse(part, item, comparisonUnitAtomList, settings);
+ return;
+ }
+
+ if (element.Name == W.t || element.Name == W.delText)
+ {
+ var val = element.Value;
+ foreach (var ch in val)
+ {
+ ComparisonUnitAtom sr = new ComparisonUnitAtom(
+ new XElement(element.Name, ch),
+ element.AncestorsAndSelf().TakeWhile(a => a.Name != W.body && a.Name != W.footnotes && a.Name != W.endnotes).Reverse().ToArray(),
+ part,
+ settings);
+ comparisonUnitAtomList.Add(sr);
+ }
+ return;
+ }
+
+ if (AllowableRunChildren.Contains(element.Name) || element.Name == W._object)
+ {
+ ComparisonUnitAtom sr3 = new ComparisonUnitAtom(
+ element,
+ element.AncestorsAndSelf().TakeWhile(a => a.Name != W.body && a.Name != W.footnotes && a.Name != W.endnotes).Reverse().ToArray(),
+ part,
+ settings);
+ comparisonUnitAtomList.Add(sr3);
+ return;
+ }
+
+ var re = RecursionElements.FirstOrDefault(z => z.ElementName == element.Name);
+ if (re != null)
+ {
+ AnnotateElementWithProps(part, element, comparisonUnitAtomList, re.ChildElementPropertyNames, settings);
+ return;
+ }
+
+ if (ElementsToThrowAway.Contains(element.Name))
+ return;
+
+ AnnotateElementWithProps(part, element, comparisonUnitAtomList, null, settings);
+ }
+
+ private static void AnnotateElementWithProps(OpenXmlPart part, XElement element, List<ComparisonUnitAtom> comparisonUnitAtomList, XName[] childElementPropertyNames, WmlComparerSettings settings)
+ {
+ IEnumerable<XElement> runChildrenToProcess = null;
+ if (childElementPropertyNames == null)
+ runChildrenToProcess = element.Elements();
+ else
+ runChildrenToProcess = element
+ .Elements()
+ .Where(e => !childElementPropertyNames.Contains(e.Name));
+
+ foreach (var item in runChildrenToProcess)
+ CreateComparisonUnitAtomListRecurse(part, item, comparisonUnitAtomList, settings);
+ }
+
+
+
+ private static void AssignUnidToAllElements(XElement contentParent)
+ {
+ var content = contentParent.Descendants();
+ foreach (var d in content)
+ {
+ if (d.Attribute(PtOpenXml.Unid) == null)
+ {
+ string unid = Guid.NewGuid().ToString().Replace("-", "");
+ var newAtt = new XAttribute(PtOpenXml.Unid, unid);
+ d.Add(newAtt);
+ }
+ }
+ }
+ }
+
+ internal class WithHierarchicalGroupingKey
+ {
+ public string[] HierarchicalGroupingArray;
+ public ComparisonUnitWord ComparisonUnitWord;
+ }
+
+ public abstract class ComparisonUnit
+ {
+ public List<ComparisonUnit> Contents;
+ public string SHA1Hash;
+ public CorrelationStatus CorrelationStatus;
+
+ public IEnumerable<ComparisonUnit> Descendants()
+ {
+ List<ComparisonUnit> comparisonUnitList = new List<ComparisonUnit>();
+ DescendantsInternal(this, comparisonUnitList);
+ return comparisonUnitList;
+ }
+
+ public IEnumerable<ComparisonUnitAtom> DescendantContentAtoms()
+ {
+ return Descendants().OfType<ComparisonUnitAtom>();
+ }
+
+ private int? m_DescendantContentAtomsCount = null;
+
+ public int DescendantContentAtomsCount
+ {
+ get
+ {
+ if (m_DescendantContentAtomsCount != null)
+ return (int)m_DescendantContentAtomsCount;
+ m_DescendantContentAtomsCount = this.DescendantContentAtoms().Count();
+ return (int)m_DescendantContentAtomsCount;
+ }
+ }
+
+ private void DescendantsInternal(ComparisonUnit comparisonUnit, List<ComparisonUnit> comparisonUnitList)
+ {
+ foreach (var cu in comparisonUnit.Contents)
+ {
+ comparisonUnitList.Add(cu);
+ if (cu.Contents != null && cu.Contents.Any())
+ DescendantsInternal(cu, comparisonUnitList);
+ }
+ }
+
+ public abstract string ToString(int indent);
+
+ internal static string ComparisonUnitListToString(ComparisonUnit[] cul)
+ {
+ var sb = new StringBuilder();
+ sb.Append("Dump Comparision Unit List To String" + Environment.NewLine);
+ foreach (var item in cul)
+ {
+ sb.Append(item.ToString(2) + Environment.NewLine);
+ }
+ return sb.ToString();
+ }
+ }
+
+ internal class ComparisonUnitWord : ComparisonUnit
+ {
+ public ComparisonUnitWord(IEnumerable<ComparisonUnitAtom> comparisonUnitAtomList)
+ {
+ Contents = comparisonUnitAtomList.OfType<ComparisonUnit>().ToList();
+ var sha1String = Contents
+ .Select(c => c.SHA1Hash)
+ .StringConcatenate();
+ SHA1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(sha1String);
+ }
+
+ public static XName[] s_ElementsWithRelationshipIds = new XName[] {
+ A.blip,
+ A.hlinkClick,
+ A.relIds,
+ C.chart,
+ C.externalData,
+ C.userShapes,
+ DGM.relIds,
+ O.OLEObject,
+ VML.fill,
+ VML.imagedata,
+ VML.stroke,
+ W.altChunk,
+ W.attachedTemplate,
+ W.control,
+ W.dataSource,
+ W.embedBold,
+ W.embedBoldItalic,
+ W.embedItalic,
+ W.embedRegular,
+ W.footerReference,
+ W.headerReference,
+ W.headerSource,
+ W.hyperlink,
+ W.printerSettings,
+ W.recipientData,
+ W.saveThroughXslt,
+ W.sourceFileName,
+ W.src,
+ W.subDoc,
+ WNE.toolbarData,
+ };
+
+ public static XName[] s_RelationshipAttributeNames = new XName[] {
+ R.embed,
+ R.link,
+ R.id,
+ R.cs,
+ R.dm,
+ R.lo,
+ R.qs,
+ R.href,
+ R.pict,
+ };
+
+ public override string ToString(int indent)
+ {
+ var sb = new StringBuilder();
+ sb.Append("".PadRight(indent) + "Word SHA1:" + this.SHA1Hash.Substring(0, 8) + Environment.NewLine);
+ foreach (var comparisonUnitAtom in Contents)
+ sb.Append(comparisonUnitAtom.ToString(indent + 2) + Environment.NewLine);
+ return sb.ToString();
+ }
+ }
+
+ class WmlComparerUtil
+ {
+ public static string SHA1HashStringForUTF8String(string s)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(s);
+ var sha1 = SHA1.Create();
+ byte[] hashBytes = sha1.ComputeHash(bytes);
+ return HexStringFromBytes(hashBytes);
+ }
+
+ public static string SHA1HashStringForByteArray(byte[] bytes)
+ {
+ var sha1 = SHA1.Create();
+ byte[] hashBytes = sha1.ComputeHash(bytes);
+ return HexStringFromBytes(hashBytes);
+ }
+
+ public static string HexStringFromBytes(byte[] bytes)
+ {
+ var sb = new StringBuilder();
+ foreach (byte b in bytes)
+ {
+ var hex = b.ToString("x2");
+ sb.Append(hex);
+ }
+ return sb.ToString();
+ }
+ }
+
+ public class ComparisonUnitAtom : ComparisonUnit
+ {
+ // AncestorElements are kept in order from the body to the leaf, because this is the order in which we need to access in order
+ // to reassemble the document. However, in many places in the code, it is necessary to find the nearest ancestor, i.e. cell
+ // so it is necessary to reverse the order when looking for it, i.e. look from the leaf back to the body element.
+
+ public XElement[] AncestorElements;
+ public string[] AncestorUnids;
+ public XElement ContentElement;
+ public XElement ContentElementBefore;
+ public ComparisonUnitAtom ComparisonUnitAtomBefore;
+ public OpenXmlPart Part;
+ public XElement RevTrackElement;
+
+ public ComparisonUnitAtom(XElement contentElement, XElement[] ancestorElements, OpenXmlPart part, WmlComparerSettings settings)
+ {
+ ContentElement = contentElement;
+ AncestorElements = ancestorElements;
+ Part = part;
+ RevTrackElement = GetRevisionTrackingElementFromAncestors(contentElement, AncestorElements);
+ if (RevTrackElement == null)
+ {
+ CorrelationStatus = CorrelationStatus.Equal;
+ }
+ else
+ {
+ if (RevTrackElement.Name == W.del)
+ CorrelationStatus = CorrelationStatus.Deleted;
+ else if (RevTrackElement.Name == W.ins)
+ CorrelationStatus = CorrelationStatus.Inserted;
+ }
+ string sha1Hash = (string)contentElement.Attribute(PtOpenXml.SHA1Hash);
+ if (sha1Hash != null)
+ {
+ SHA1Hash = sha1Hash;
+ }
+ else
+ {
+ var shaHashString = GetSha1HashStringForElement(ContentElement, settings);
+ SHA1Hash = WmlComparerUtil.SHA1HashStringForUTF8String(shaHashString);
+ }
+ }
+
+ private string GetSha1HashStringForElement(XElement contentElement, WmlComparerSettings settings)
+ {
+ var text = contentElement.Value;
+ if (settings.CaseInsensitive)
+ text = text.ToUpper(settings.CultureInfo);
+ return contentElement.Name.LocalName + text;
+ }
+
+ private static XElement GetRevisionTrackingElementFromAncestors(XElement contentElement, XElement[] ancestors)
+ {
+ XElement revTrackElement = null;
+
+ if (contentElement.Name == W.pPr)
+ {
+ revTrackElement = contentElement
+ .Elements(W.rPr)
+ .Elements()
+ .FirstOrDefault(e => e.Name == W.del || e.Name == W.ins);
+ return revTrackElement;
+ }
+
+ revTrackElement = ancestors.FirstOrDefault(a => a.Name == W.del || a.Name == W.ins);
+ return revTrackElement;
+ }
+
+ public override string ToString(int indent)
+ {
+ int xNamePad = 16;
+ var indentString = "".PadRight(indent);
+
+ var sb = new StringBuilder();
+ sb.Append(indentString);
+ string correlationStatus = "";
+ if (CorrelationStatus != OpenXmlPowerTools.CorrelationStatus.Nil)
+ correlationStatus = string.Format("[{0}] ", CorrelationStatus.ToString().PadRight(8));
+ if (ContentElement.Name == W.t || ContentElement.Name == W.delText)
+ {
+ sb.AppendFormat("Atom {0}: {1} {2} SHA1:{3} ", PadLocalName(xNamePad, this), ContentElement.Value, correlationStatus, this.SHA1Hash.Substring(0, 8));
+ AppendAncestorsDump(sb, this);
+ }
+ else
+ {
+ sb.AppendFormat("Atom {0}: {1} SHA1:{2} ", PadLocalName(xNamePad, this), correlationStatus, this.SHA1Hash.Substring(0, 8));
+ AppendAncestorsDump(sb, this);
+ }
+ return sb.ToString();
+ }
+
+ public string ToStringAncestorUnids(int indent)
+ {
+ int xNamePad = 16;
+ var indentString = "".PadRight(indent);
+
+ var sb = new StringBuilder();
+ sb.Append(indentString);
+ string correlationStatus = "";
+ if (CorrelationStatus != OpenXmlPowerTools.CorrelationStatus.Nil)
+ correlationStatus = string.Format("[{0}] ", CorrelationStatus.ToString().PadRight(8));
+ if (ContentElement.Name == W.t || ContentElement.Name == W.delText)
+ {
+ sb.AppendFormat("Atom {0}: {1} {2} SHA1:{3} ", PadLocalName(xNamePad, this), ContentElement.Value, correlationStatus, this.SHA1Hash.Substring(0, 8));
+ AppendAncestorsUnidsDump(sb, this);
+ }
+ else
+ {
+ sb.AppendFormat("Atom {0}: {1} SHA1:{2} ", PadLocalName(xNamePad, this), correlationStatus, this.SHA1Hash.Substring(0, 8));
+ AppendAncestorsUnidsDump(sb, this);
+ }
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ return ToString(0);
+ }
+
+ public string ToStringAncestorUnids()
+ {
+ return ToStringAncestorUnids(0);
+ }
+
+ private static string PadLocalName(int xNamePad, ComparisonUnitAtom item)
+ {
+ return (item.ContentElement.Name.LocalName + " ").PadRight(xNamePad, '-') + " ";
+ }
+
+ private void AppendAncestorsDump(StringBuilder sb, ComparisonUnitAtom sr)
+ {
+ var s = sr.AncestorElements.Select(p => p.Name.LocalName + GetUnid(p) + "/").StringConcatenate().TrimEnd('/');
+ sb.Append("Ancestors:" + s);
+ }
+
+ private void AppendAncestorsUnidsDump(StringBuilder sb, ComparisonUnitAtom sr)
+ {
+ var zipped = sr.AncestorElements.Zip(sr.AncestorUnids, (a, u) => new
+ {
+ AncestorElement = a,
+ AncestorUnid = u,
+ });
+ var s = zipped.Select(p => p.AncestorElement.Name.LocalName + "[" + p.AncestorUnid.Substring(0, 8) + "]/").StringConcatenate().TrimEnd('/');
+ sb.Append("Ancestors:" + s);
+ }
+
+ private string GetUnid(XElement p)
+ {
+ var unid = (string)p.Attribute(PtOpenXml.Unid);
+ if (unid == null)
+ return "";
+ return "[" + unid.Substring(0, 8) + "]";
+ }
+
+ public static string ComparisonUnitAtomListToString(List<ComparisonUnitAtom> comparisonUnitAtomList, int indent)
+ {
+ StringBuilder sb = new StringBuilder();
+ var cal = comparisonUnitAtomList
+ .Select((ca, i) => new
+ {
+ ComparisonUnitAtom = ca,
+ Index = i,
+ });
+ foreach (var item in cal)
+ sb.Append("".PadRight(indent))
+ .AppendFormat("[{0:000000}] ", item.Index + 1)
+ .Append(item.ComparisonUnitAtom.ToString(0) + Environment.NewLine);
+ return sb.ToString();
+ }
+ }
+
+ internal enum ComparisonUnitGroupType
+ {
+ Paragraph,
+ Table,
+ Row,
+ Cell,
+ Textbox,
+ };
+
+ internal class ComparisonUnitGroup : ComparisonUnit
+ {
+ public ComparisonUnitGroupType ComparisonUnitGroupType;
+ public string CorrelatedSHA1Hash;
+ public string StructureSHA1Hash;
+
+ public ComparisonUnitGroup(IEnumerable<ComparisonUnit> comparisonUnitList, ComparisonUnitGroupType groupType, int level)
+ {
+ Contents = comparisonUnitList.ToList();
+ ComparisonUnitGroupType = groupType;
+ var first = comparisonUnitList.First();
+ ComparisonUnitAtom comparisonUnitAtom = GetFirstComparisonUnitAtomOfGroup(first);
+ XName ancestorName = null;
+ if (groupType == OpenXmlPowerTools.ComparisonUnitGroupType.Table)
+ ancestorName = W.tbl;
+ else if (groupType == OpenXmlPowerTools.ComparisonUnitGroupType.Row)
+ ancestorName = W.tr;
+ else if (groupType == OpenXmlPowerTools.ComparisonUnitGroupType.Cell)
+ ancestorName = W.tc;
+ else if (groupType == OpenXmlPowerTools.ComparisonUnitGroupType.Paragraph)
+ ancestorName = W.p;
+ else if (groupType == OpenXmlPowerTools.ComparisonUnitGroupType.Textbox)
+ ancestorName = W.txbxContent;
+
+ var ancestorsToLookAt = comparisonUnitAtom.AncestorElements.Where(ae => ae.Name == W.tbl || ae.Name == W.tr || ae.Name == W.tc || ae.Name == W.p || ae.Name == W.txbxContent).ToArray(); ;
+ var ancestor = ancestorsToLookAt[level];
+
+ if (ancestor == null)
+ throw new OpenXmlPowerToolsException("Internal error: ComparisonUnitGroup");
+ SHA1Hash = (string)ancestor.Attribute(PtOpenXml.SHA1Hash);
+ CorrelatedSHA1Hash = (string)ancestor.Attribute(PtOpenXml.CorrelatedSHA1Hash);
+ StructureSHA1Hash = (string)ancestor.Attribute(PtOpenXml.StructureSHA1Hash);
+ }
+
+ public static ComparisonUnitAtom GetFirstComparisonUnitAtomOfGroup(ComparisonUnit group)
+ {
+ var thisGroup = group;
+ while (true)
+ {
+ var tg = thisGroup as ComparisonUnitGroup;
+ if (tg != null)
+ {
+ thisGroup = tg.Contents.First();
+ continue;
+ }
+ var tw = thisGroup as ComparisonUnitWord;
+ if (tw == null)
+ throw new OpenXmlPowerToolsException("Internal error: GetFirstComparisonUnitAtomOfGroup");
+ var ca = (ComparisonUnitAtom)tw.Contents.First();
+ return ca;
+ }
+ }
+
+ public override string ToString(int indent)
+ {
+ var sb = new StringBuilder();
+ sb.Append("".PadRight(indent) + "Group Type: " + ComparisonUnitGroupType.ToString() + " SHA1:" + SHA1Hash + Environment.NewLine);
+ foreach (var comparisonUnitAtom in Contents)
+ sb.Append(comparisonUnitAtom.ToString(indent + 2));
+ return sb.ToString();
+ }
+ }
+
+ public enum CorrelationStatus
+ {
+ Nil,
+ Normal,
+ Unknown,
+ Inserted,
+ Deleted,
+ Equal,
+ Group,
+ }
+
+ class PartSHA1HashAnnotation
+ {
+ public string Hash;
+
+ public PartSHA1HashAnnotation(string hash)
+ {
+ Hash = hash;
+ }
+ }
+
+ class CorrelatedSequence
+ {
+ public CorrelationStatus CorrelationStatus;
+
+ // if ComparisonUnitList1 == null and ComparisonUnitList2 contains sequence, then inserted content.
+ // if ComparisonUnitList2 == null and ComparisonUnitList1 contains sequence, then deleted content.
+ // if ComparisonUnitList2 contains sequence and ComparisonUnitList1 contains sequence, then either is Unknown or Equal.
+ public ComparisonUnit[] ComparisonUnitArray1;
+ public ComparisonUnit[] ComparisonUnitArray2;
+#if DEBUG
+ public string SourceFile;
+ public int SourceLine;
+#endif
+
+ public CorrelatedSequence()
+ {
+#if DEBUG
+ SourceFile = new System.Diagnostics.StackTrace(true).GetFrame(1).GetFileName();
+ SourceLine = new System.Diagnostics.StackTrace(true).GetFrame(1).GetFileLineNumber();
+#endif
+ }
+
+ public override string ToString()
+ {
+ var sb = new StringBuilder();
+ var indentString = " ";
+ var indentString4 = " ";
+ sb.Append("CorrelatedSequence =====" + Environment.NewLine);
+#if DEBUG
+ sb.Append(indentString + "Created at Line: " + SourceLine.ToString() + Environment.NewLine);
+#endif
+ sb.Append(indentString + "CorrelatedItem =====" + Environment.NewLine);
+ sb.Append(indentString4 + "CorrelationStatus: " + CorrelationStatus.ToString() + Environment.NewLine);
+ if (CorrelationStatus == OpenXmlPowerTools.CorrelationStatus.Equal)
+ {
+ sb.Append(indentString4 + "ComparisonUnitList =====" + Environment.NewLine);
+ foreach (var item in ComparisonUnitArray2)
+ sb.Append(item.ToString(6) + Environment.NewLine);
+ }
+ else
+ {
+ if (ComparisonUnitArray1 != null)
+ {
+ sb.Append(indentString4 + "ComparisonUnitList1 =====" + Environment.NewLine);
+ foreach (var item in ComparisonUnitArray1)
+ sb.Append(item.ToString(6) + Environment.NewLine);
+ }
+ if (ComparisonUnitArray2 != null)
+ {
+ sb.Append(indentString4 + "ComparisonUnitList2 =====" + Environment.NewLine);
+ foreach (var item in ComparisonUnitArray2)
+ sb.Append(item.ToString(6) + Environment.NewLine);
+ }
+ }
+ return sb.ToString();
+ }
+ }
+}
+
diff --git a/OpenXmlPowerTools/WmlDocument.cs b/OpenXmlPowerTools/WmlDocument.cs
new file mode 100644
index 0000000..40e36d7
--- /dev/null
+++ b/OpenXmlPowerTools/WmlDocument.cs
@@ -0,0 +1,121 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Packaging;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+namespace OpenXmlPowerTools
+{
+ public class PtMainDocumentPart : XElement
+ {
+ private WmlDocument ParentWmlDocument;
+
+ public PtWordprocessingCommentsPart WordprocessingCommentsPart
+ {
+ get
+ {
+ using (MemoryStream ms = new MemoryStream(ParentWmlDocument.DocumentByteArray))
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, false))
+ {
+ WordprocessingCommentsPart commentsPart = wDoc.MainDocumentPart.WordprocessingCommentsPart;
+ if (commentsPart == null)
+ return null;
+ XElement partElement = commentsPart.GetXDocument().Root;
+ var childNodes = partElement.Nodes().ToList();
+ foreach (var item in childNodes)
+ item.Remove();
+ return new PtWordprocessingCommentsPart(this.ParentWmlDocument, commentsPart.Uri, partElement.Name, partElement.Attributes(), childNodes);
+ }
+ }
+ }
+
+ public PtMainDocumentPart(WmlDocument wmlDocument, Uri uri, XName name, params object[] values)
+ : base(name, values)
+ {
+ ParentWmlDocument = wmlDocument;
+ this.Add(
+ new XAttribute(PtOpenXml.Uri, uri),
+ new XAttribute(XNamespace.Xmlns + "pt", PtOpenXml.pt)
+ );
+ }
+ }
+
+ public class PtWordprocessingCommentsPart : XElement
+ {
+ private WmlDocument ParentWmlDocument;
+
+ public PtWordprocessingCommentsPart(WmlDocument wmlDocument, Uri uri, XName name, params object[] values)
+ : base(name, values)
+ {
+ ParentWmlDocument = wmlDocument;
+ this.Add(
+ new XAttribute(PtOpenXml.Uri, uri),
+ new XAttribute(XNamespace.Xmlns + "pt", PtOpenXml.pt)
+ );
+ }
+ }
+
+ public partial class WmlDocument
+ {
+ public PtMainDocumentPart MainDocumentPart
+ {
+ get
+ {
+ using (MemoryStream ms = new MemoryStream(this.DocumentByteArray))
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, false))
+ {
+ XElement partElement = wDoc.MainDocumentPart.GetXDocument().Root;
+ var childNodes = partElement.Nodes().ToList();
+ foreach (var item in childNodes)
+ item.Remove();
+ return new PtMainDocumentPart(this, wDoc.MainDocumentPart.Uri, partElement.Name, partElement.Attributes(), childNodes);
+ }
+ }
+ }
+
+ public WmlDocument(WmlDocument other, params XElement[] replacementParts)
+ : base(other)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(this))
+ {
+ using (Package package = streamDoc.GetPackage())
+ {
+ foreach (var replacementPart in replacementParts)
+ {
+ XAttribute uriAttribute = replacementPart.Attribute(PtOpenXml.Uri);
+ if (uriAttribute == null)
+ throw new OpenXmlPowerToolsException("Replacement part does not contain a Uri as an attribute");
+ String uri = uriAttribute.Value;
+ var part = package.GetParts().FirstOrDefault(p => p.Uri.ToString() == uri);
+ using (Stream partStream = part.GetStream(FileMode.Create, FileAccess.Write))
+ using (XmlWriter partXmlWriter = XmlWriter.Create(partStream))
+ replacementPart.Save(partXmlWriter);
+ }
+ }
+ this.DocumentByteArray = streamDoc.GetModifiedDocument().DocumentByteArray;
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/WmlToHtmlConverter.cs b/OpenXmlPowerTools/WmlToHtmlConverter.cs
new file mode 100644
index 0000000..e555ba1
--- /dev/null
+++ b/OpenXmlPowerTools/WmlToHtmlConverter.cs
@@ -0,0 +1,3338 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+Version: 3.1.12
+ * Improve layout of list items, using "display: inline-block" with a width rule.
+ * Streamline HTML, omitting unnecessary formatting-related HTML (e.g., <b>bold</b>, <i>italic</i>).
+ * Use HTML5 instead of HTML4 meta tags (e.g., for charset).
+ * Produce correctly formatted decimal numbers (e.g., "0.5in" instead of "0,5in") also for non-US locales.
+ * Produce correct color rules (e.g., "#000000" instead of "#auto").
+ * Added quick fix to CalcWidthOfRunInTwips to better accommodate entities in the layout.
+ * Make ProcessImage() publicly accessible.
+ * Refactor and streamline code.
+
+Version: 2.7.03
+ * Support for RTL languages.
+
+Version: 2.7.00
+ * Uses new ListItemRetriever.
+ * Better support for RTL languages.
+
+Version: 2.6.01
+ * Add languageCultureName parameter to GetListItemText methods. This enables a single implementation to handle
+ more than one language/culture where appropriate.
+
+Version: 2.6.00
+ * Re-write to support styles and rich content
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+
+// 200e lrm - LTR
+// 200f rlm - RTL
+
+// todo need to set the HTTP "Content-Language" header, for instance:
+// Content-Language: en-US
+// Content-Language: fr-FR
+
+namespace OpenXmlPowerTools
+{
+ public partial class WmlDocument
+ {
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public XElement ConvertToHtml(WmlToHtmlConverterSettings htmlConverterSettings)
+ {
+ return WmlToHtmlConverter.ConvertToHtml(this, htmlConverterSettings);
+ }
+
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public XElement ConvertToHtml(HtmlConverterSettings htmlConverterSettings)
+ {
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings(htmlConverterSettings);
+ return WmlToHtmlConverter.ConvertToHtml(this, settings);
+ }
+ }
+
+ [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
+ public class WmlToHtmlConverterSettings
+ {
+ public string PageTitle;
+ public string CssClassPrefix;
+ public bool FabricateCssClasses;
+ public string GeneralCss;
+ public string AdditionalCss;
+ public bool RestrictToSupportedLanguages;
+ public bool RestrictToSupportedNumberingFormats;
+ public Dictionary<string, Func<string, int, string, string>> ListItemImplementations;
+ public Func<ImageInfo, XElement> ImageHandler;
+
+ public WmlToHtmlConverterSettings()
+ {
+ PageTitle = "";
+ CssClassPrefix = "pt-";
+ FabricateCssClasses = true;
+ GeneralCss = "span { white-space: pre-wrap; }";
+ AdditionalCss = "";
+ RestrictToSupportedLanguages = false;
+ RestrictToSupportedNumberingFormats = false;
+ ListItemImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations;
+ }
+
+ public WmlToHtmlConverterSettings(HtmlConverterSettings htmlConverterSettings)
+ {
+ PageTitle = htmlConverterSettings.PageTitle;
+ CssClassPrefix = htmlConverterSettings.CssClassPrefix;
+ FabricateCssClasses = htmlConverterSettings.FabricateCssClasses;
+ GeneralCss = htmlConverterSettings.GeneralCss;
+ AdditionalCss = htmlConverterSettings.AdditionalCss;
+ RestrictToSupportedLanguages = htmlConverterSettings.RestrictToSupportedLanguages;
+ RestrictToSupportedNumberingFormats = htmlConverterSettings.RestrictToSupportedNumberingFormats;
+ ListItemImplementations = htmlConverterSettings.ListItemImplementations;
+ ImageHandler = htmlConverterSettings.ImageHandler;
+ }
+ }
+
+ [SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Global")]
+ public class HtmlConverterSettings
+ {
+ public string PageTitle;
+ public string CssClassPrefix;
+ public bool FabricateCssClasses;
+ public string GeneralCss;
+ public string AdditionalCss;
+ public bool RestrictToSupportedLanguages;
+ public bool RestrictToSupportedNumberingFormats;
+ public Dictionary<string, Func<string, int, string, string>> ListItemImplementations;
+ public Func<ImageInfo, XElement> ImageHandler;
+
+ public HtmlConverterSettings()
+ {
+ PageTitle = "";
+ CssClassPrefix = "pt-";
+ FabricateCssClasses = true;
+ GeneralCss = "span { white-space: pre-wrap; }";
+ AdditionalCss = "";
+ RestrictToSupportedLanguages = false;
+ RestrictToSupportedNumberingFormats = false;
+ ListItemImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations;
+ }
+ }
+
+ public static class HtmlConverter
+ {
+ public static XElement ConvertToHtml(WmlDocument wmlDoc, HtmlConverterSettings htmlConverterSettings)
+ {
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings(htmlConverterSettings);
+ return WmlToHtmlConverter.ConvertToHtml(wmlDoc, settings);
+ }
+
+ public static XElement ConvertToHtml(WordprocessingDocument wDoc, HtmlConverterSettings htmlConverterSettings)
+ {
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings(htmlConverterSettings);
+ return WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+ }
+ }
+
+ [SuppressMessage("ReSharper", "NotAccessedField.Global")]
+ [SuppressMessage("ReSharper", "UnusedMember.Global")]
+ public class ImageInfo
+ {
+ public Bitmap Bitmap;
+ public XAttribute ImgStyleAttribute;
+ public string ContentType;
+ public XElement DrawingElement;
+ public string AltText;
+
+ public const int EmusPerInch = 914400;
+ public const int EmusPerCm = 360000;
+ }
+
+ public static class WmlToHtmlConverter
+ {
+ public static XElement ConvertToHtml(WmlDocument doc, WmlToHtmlConverterSettings htmlConverterSettings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc))
+ {
+ using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument())
+ {
+ return ConvertToHtml(document, htmlConverterSettings);
+ }
+ }
+ }
+
+ public static XElement ConvertToHtml(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings htmlConverterSettings)
+ {
+ RevisionAccepter.AcceptRevisions(wordDoc);
+ SimplifyMarkupSettings simplifyMarkupSettings = new SimplifyMarkupSettings
+ {
+ RemoveComments = true,
+ RemoveContentControls = true,
+ RemoveEndAndFootNotes = true,
+ RemoveFieldCodes = false,
+ RemoveLastRenderedPageBreak = true,
+ RemovePermissions = true,
+ RemoveProof = true,
+ RemoveRsidInfo = true,
+ RemoveSmartTags = true,
+ RemoveSoftHyphens = true,
+ RemoveGoBackBookmark = true,
+ ReplaceTabsWithSpaces = false,
+ };
+ MarkupSimplifier.SimplifyMarkup(wordDoc, simplifyMarkupSettings);
+
+ FormattingAssemblerSettings formattingAssemblerSettings = new FormattingAssemblerSettings
+ {
+ RemoveStyleNamesFromParagraphAndRunProperties = false,
+ ClearStyles = false,
+ RestrictToSupportedLanguages = htmlConverterSettings.RestrictToSupportedLanguages,
+ RestrictToSupportedNumberingFormats = htmlConverterSettings.RestrictToSupportedNumberingFormats,
+ CreateHtmlConverterAnnotationAttributes = true,
+ OrderElementsPerStandard = false,
+ ListItemRetrieverSettings =
+ htmlConverterSettings.ListItemImplementations == null ?
+ new ListItemRetrieverSettings()
+ {
+ ListItemTextImplementations = ListItemRetrieverSettings.DefaultListItemTextImplementations,
+ } :
+ new ListItemRetrieverSettings()
+ {
+ ListItemTextImplementations = htmlConverterSettings.ListItemImplementations,
+ },
+ };
+
+ FormattingAssembler.AssembleFormatting(wordDoc, formattingAssemblerSettings);
+
+ InsertAppropriateNonbreakingSpaces(wordDoc);
+ CalculateSpanWidthForTabs(wordDoc);
+ ReverseTableBordersForRtlTables(wordDoc);
+ AdjustTableBorders(wordDoc);
+ XElement rootElement = wordDoc.MainDocumentPart.GetXDocument().Root;
+ FieldRetriever.AnnotateWithFieldInfo(wordDoc.MainDocumentPart);
+ AnnotateForSections(wordDoc);
+
+ XElement xhtml = (XElement)ConvertToHtmlTransform(wordDoc, htmlConverterSettings,
+ rootElement, false, 0m);
+
+ ReifyStylesAndClasses(htmlConverterSettings, xhtml);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ return xhtml;
+ }
+
+ private static void ReverseTableBordersForRtlTables(WordprocessingDocument wordDoc)
+ {
+ XDocument xd = wordDoc.MainDocumentPart.GetXDocument();
+ foreach (var tbl in xd.Descendants(W.tbl))
+ {
+ var bidiVisual = tbl.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
+ if (bidiVisual == null)
+ continue;
+
+ var tblBorders = tbl.Elements(W.tblPr).Elements(W.tblBorders).FirstOrDefault();
+ if (tblBorders != null)
+ {
+ var left = tblBorders.Element(W.left);
+ if (left != null)
+ left = new XElement(W.right, left.Attributes());
+
+ var right = tblBorders.Element(W.right);
+ if (right != null)
+ right = new XElement(W.left, right.Attributes());
+
+ var newTblBorders = new XElement(W.tblBorders,
+ tblBorders.Element(W.top),
+ left,
+ tblBorders.Element(W.bottom),
+ right);
+ tblBorders.ReplaceWith(newTblBorders);
+ }
+
+ foreach (var tc in tbl.Elements(W.tr).Elements(W.tc))
+ {
+ var tcBorders = tc.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault();
+ if (tcBorders != null)
+ {
+ var left = tcBorders.Element(W.left);
+ if (left != null)
+ left = new XElement(W.right, left.Attributes());
+
+ var right = tcBorders.Element(W.right);
+ if (right != null)
+ right = new XElement(W.left, right.Attributes());
+
+ var newTcBorders = new XElement(W.tcBorders,
+ tcBorders.Element(W.top),
+ left,
+ tcBorders.Element(W.bottom),
+ right);
+ tcBorders.ReplaceWith(newTcBorders);
+ }
+ }
+ }
+ }
+
+ private static void ReifyStylesAndClasses(WmlToHtmlConverterSettings htmlConverterSettings, XElement xhtml)
+ {
+ if (htmlConverterSettings.FabricateCssClasses)
+ {
+ var usedCssClassNames = new HashSet<string>();
+ var elementsThatNeedClasses = xhtml
+ .DescendantsAndSelf()
+ .Select(d => new
+ {
+ Element = d,
+ Styles = d.Annotation<Dictionary<string, string>>(),
+ })
+ .Where(z => z.Styles != null);
+ var augmented = elementsThatNeedClasses
+ .Select(p => new
+ {
+ p.Element,
+ p.Styles,
+ StylesString = p.Element.Name.LocalName + "|" + p.Styles.OrderBy(k => k.Key).Select(s => string.Format("{0}: {1};", s.Key, s.Value)).StringConcatenate(),
+ })
+ .GroupBy(p => p.StylesString)
+ .ToList();
+ int classCounter = 1000000;
+ var sb = new StringBuilder();
+ sb.Append(Environment.NewLine);
+ foreach (var grp in augmented)
+ {
+ string classNameToUse;
+ var firstOne = grp.First();
+ var styles = firstOne.Styles;
+ if (styles.ContainsKey("PtStyleName"))
+ {
+ classNameToUse = htmlConverterSettings.CssClassPrefix + styles["PtStyleName"];
+ if (usedCssClassNames.Contains(classNameToUse))
+ {
+ classNameToUse = htmlConverterSettings.CssClassPrefix +
+ styles["PtStyleName"] + "-" +
+ classCounter.ToString().Substring(1);
+ classCounter++;
+ }
+ }
+ else
+ {
+ classNameToUse = htmlConverterSettings.CssClassPrefix +
+ classCounter.ToString().Substring(1);
+ classCounter++;
+ }
+ usedCssClassNames.Add(classNameToUse);
+ sb.Append(firstOne.Element.Name.LocalName + "." + classNameToUse + " {" + Environment.NewLine);
+ foreach (var st in firstOne.Styles.Where(s => s.Key != "PtStyleName"))
+ {
+ var s = " " + st.Key + ": " + st.Value + ";" + Environment.NewLine;
+ sb.Append(s);
+ }
+ sb.Append("}" + Environment.NewLine);
+ var classAtt = new XAttribute("class", classNameToUse);
+ foreach (var gc in grp)
+ gc.Element.Add(classAtt);
+ }
+ var styleValue = htmlConverterSettings.GeneralCss + sb + htmlConverterSettings.AdditionalCss;
+
+ SetStyleElementValue(xhtml, styleValue);
+ }
+ else
+ {
+ // Previously, the h:style element was not added at this point. However,
+ // at least the General CSS will contain important settings.
+ SetStyleElementValue(xhtml, htmlConverterSettings.GeneralCss + htmlConverterSettings.AdditionalCss);
+
+ foreach (var d in xhtml.DescendantsAndSelf())
+ {
+ var style = d.Annotation<Dictionary<string, string>>();
+ if (style == null)
+ continue;
+ var styleValue =
+ style
+ .Where(p => p.Key != "PtStyleName")
+ .OrderBy(p => p.Key)
+ .Select(e => string.Format("{0}: {1};", e.Key, e.Value))
+ .StringConcatenate();
+ XAttribute st = new XAttribute("style", styleValue);
+ if (d.Attribute("style") != null)
+ d.Attribute("style").Value += styleValue;
+ else
+ d.Add(st);
+ }
+ }
+ }
+
+ private static void SetStyleElementValue(XElement xhtml, string styleValue)
+ {
+ var styleElement = xhtml
+ .Descendants(Xhtml.style)
+ .FirstOrDefault();
+ if (styleElement != null)
+ styleElement.Value = styleValue;
+ else
+ {
+ styleElement = new XElement(Xhtml.style, styleValue);
+ var head = xhtml.Element(Xhtml.head);
+ if (head != null)
+ head.Add(styleElement);
+ }
+ }
+
+ private static object ConvertToHtmlTransform(WordprocessingDocument wordDoc,
+ WmlToHtmlConverterSettings settings, XNode node,
+ bool suppressTrailingWhiteSpace,
+ decimal currentMarginLeft)
+ {
+ var element = node as XElement;
+ if (element == null) return null;
+
+ // Transform the w:document element to the XHTML h:html element.
+ // The h:head element is laid out based on the W3C's recommended layout, i.e.,
+ // the charset (using the HTML5-compliant form), the title (which is always
+ // there but possibly empty), and other meta tags.
+ if (element.Name == W.document)
+ {
+ return new XElement(Xhtml.html,
+ new XElement(Xhtml.head,
+ new XElement(Xhtml.meta, new XAttribute("charset", "UTF-8")),
+ settings.PageTitle != null
+ ? new XElement(Xhtml.title, new XText(settings.PageTitle))
+ : new XElement(Xhtml.title, new XText(string.Empty)),
+ new XElement(Xhtml.meta,
+ new XAttribute("name", "Generator"),
+ new XAttribute("content", "PowerTools for Open XML"))),
+ element.Elements()
+ .Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft)));
+ }
+
+ // Transform the w:body element to the XHTML h:body element.
+ if (element.Name == W.body)
+ {
+ return new XElement(Xhtml.body, CreateSectionDivs(wordDoc, settings, element));
+ }
+
+ // Transform the w:p element to the XHTML h:h1-h6 or h:p element (if the previous paragraph does not
+ // have a style separator).
+ if (element.Name == W.p)
+ {
+ return ProcessParagraph(wordDoc, settings, element, suppressTrailingWhiteSpace, currentMarginLeft);
+ }
+
+ // Transform hyperlinks to the XHTML h:a element.
+ if (element.Name == W.hyperlink && element.Attribute(R.id) != null)
+ {
+ try
+ {
+ var a = new XElement(Xhtml.a,
+ new XAttribute("href",
+ wordDoc.MainDocumentPart
+ .HyperlinkRelationships
+ .First(x => x.Id == (string)element.Attribute(R.id))
+ .Uri
+ ),
+ element.Elements(W.r).Select(run => ConvertRun(wordDoc, settings, run))
+ );
+ if (!a.Nodes().Any())
+ a.Add(new XText(""));
+ return a;
+ }
+ catch (UriFormatException)
+ {
+ return element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft));
+ }
+ }
+
+ // Transform hyperlinks to bookmarks to the XHTML h:a element.
+ if (element.Name == W.hyperlink && element.Attribute(W.anchor) != null)
+ {
+ return ProcessHyperlinkToBookmark(wordDoc, settings, element);
+ }
+
+ // Transform contents of runs.
+ if (element.Name == W.r)
+ {
+ return ConvertRun(wordDoc, settings, element);
+ }
+
+ // Transform w:bookmarkStart into anchor
+ if (element.Name == W.bookmarkStart)
+ {
+ return ProcessBookmarkStart(element);
+ }
+
+ // Transform every w:t element to a text node.
+ if (element.Name == W.t)
+ {
+ // We don't need to convert characters to entities in a UTF-8 document.
+ // Further, we don't need entities for significant whitespace
+ // because we are wrapping the text nodes in <span> elements within
+ // which all whitespace is significant.
+ return new XText(element.Value);
+ }
+
+ // Transform symbols to spans
+ if (element.Name == W.sym)
+ {
+ var cs = (string)element.Attribute(W._char);
+ var c = Convert.ToInt32(cs, 16);
+ return new XElement(Xhtml.span, new XEntity(string.Format("#{0}", c)));
+ }
+
+ // Transform tabs that have the pt:TabWidth attribute set
+ if (element.Name == W.tab)
+ {
+ return ProcessTab(element);
+ }
+
+ // Transform w:br to h:br.
+ if (element.Name == W.br || element.Name == W.cr)
+ {
+ return ProcessBreak(element);
+ }
+
+ // Transform w:noBreakHyphen to '-'
+ if (element.Name == W.noBreakHyphen)
+ {
+ return new XText("-");
+ }
+
+ // Transform w:tbl to h:tbl.
+ if (element.Name == W.tbl)
+ {
+ return ProcessTable(wordDoc, settings, element, currentMarginLeft);
+ }
+
+ // Transform w:tr to h:tr.
+ if (element.Name == W.tr)
+ {
+ return ProcessTableRow(wordDoc, settings, element, currentMarginLeft);
+ }
+
+ // Transform w:tc to h:td.
+ if (element.Name == W.tc)
+ {
+ return ProcessTableCell(wordDoc, settings, element);
+ }
+
+ // Transform images
+ if (element.Name == W.drawing || element.Name == W.pict || element.Name == W._object)
+ {
+ return ProcessImage(wordDoc, element, settings.ImageHandler);
+ }
+
+ // Transform content controls.
+ if (element.Name == W.sdt)
+ {
+ return ProcessContentControl(wordDoc, settings, element, currentMarginLeft);
+ }
+
+ // Transform smart tags and simple fields.
+ if (element.Name == W.smartTag || element.Name == W.fldSimple)
+ {
+ return CreateBorderDivs(wordDoc, settings, element.Elements());
+ }
+
+ // Ignore element.
+ return null;
+ }
+
+ private static object ProcessHyperlinkToBookmark(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element)
+ {
+ var style = new Dictionary<string, string>();
+ var a = new XElement(Xhtml.a,
+ new XAttribute("href", "#" + (string) element.Attribute(W.anchor)),
+ element.Elements(W.r).Select(run => ConvertRun(wordDoc, settings, run)));
+ if (!a.Nodes().Any())
+ a.Add(new XText(""));
+ style.Add("text-decoration", "none");
+ a.AddAnnotation(style);
+ return a;
+ }
+
+ private static object ProcessBookmarkStart(XElement element)
+ {
+ var name = (string) element.Attribute(W.name);
+ if (name == null) return null;
+
+ var style = new Dictionary<string, string>();
+ var a = new XElement(Xhtml.a,
+ new XAttribute("id", name),
+ new XText(""));
+ if (!a.Nodes().Any())
+ a.Add(new XText(""));
+ style.Add("text-decoration", "none");
+ a.AddAnnotation(style);
+ return a;
+ }
+
+ private static object ProcessTab(XElement element)
+ {
+ var tabWidthAtt = element.Attribute(PtOpenXml.TabWidth);
+ if (tabWidthAtt == null) return null;
+
+ var leader = (string) element.Attribute(PtOpenXml.Leader);
+ var tabWidth = (decimal) tabWidthAtt;
+ var style = new Dictionary<string, string>();
+ XElement span;
+ if (leader != null)
+ {
+ var leaderChar = ".";
+ if (leader == "hyphen")
+ leaderChar = "-";
+ else if (leader == "dot")
+ leaderChar = ".";
+ else if (leader == "underscore")
+ leaderChar = "_";
+
+ var runContainingTabToReplace = element.Ancestors(W.r).First();
+ var fontNameAtt = runContainingTabToReplace.Attribute(PtOpenXml.pt + "FontName") ??
+ runContainingTabToReplace.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
+
+ var dummyRun = new XElement(W.r,
+ fontNameAtt,
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, leaderChar));
+
+ var widthOfLeaderChar = CalcWidthOfRunInTwips(dummyRun);
+
+ bool forceArial = false;
+ if (widthOfLeaderChar == 0)
+ {
+ dummyRun = new XElement(W.r,
+ new XAttribute(PtOpenXml.FontName, "Arial"),
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, leaderChar));
+ widthOfLeaderChar = CalcWidthOfRunInTwips(dummyRun);
+ forceArial = true;
+ }
+
+ if (widthOfLeaderChar != 0)
+ {
+ var numberOfLeaderChars = (int) (Math.Floor((tabWidth*1440)/widthOfLeaderChar));
+ if (numberOfLeaderChars < 0)
+ numberOfLeaderChars = 0;
+ span = new XElement(Xhtml.span,
+ new XAttribute(XNamespace.Xml + "space", "preserve"),
+ " " + "".PadRight(numberOfLeaderChars, leaderChar[0]) + " ");
+ style.Add("margin", "0 0 0 0");
+ style.Add("padding", "0 0 0 0");
+ style.Add("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", tabWidth));
+ style.Add("text-align", "center");
+ if (forceArial)
+ style.Add("font-family", "Arial");
+ }
+ else
+ {
+ span = new XElement(Xhtml.span, new XAttribute(XNamespace.Xml + "space", "preserve"), " ");
+ style.Add("margin", "0 0 0 0");
+ style.Add("padding", "0 0 0 0");
+ style.Add("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", tabWidth));
+ style.Add("text-align", "center");
+ if (leader == "underscore")
+ {
+ style.Add("text-decoration", "underline");
+ }
+ }
+ }
+ else
+ {
+#if false
+ var bidi = element
+ .Ancestors(W.p)
+ .Take(1)
+ .Elements(W.pPr)
+ .Elements(W.bidi)
+ .Where(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true)
+ .FirstOrDefault();
+ var isBidi = bidi != null;
+ if (isBidi)
+ span = new XElement(Xhtml.span, new XEntity("#x200f")); // RLM
+ else
+ span = new XElement(Xhtml.span, new XEntity("#x200e")); // LRM
+#else
+ span = new XElement(Xhtml.span, new XEntity("#x00a0"));
+#endif
+ style.Add("margin", string.Format(NumberFormatInfo.InvariantInfo, "0 0 0 {0:0.00}in", tabWidth));
+ style.Add("padding", "0 0 0 0");
+ }
+ span.AddAnnotation(style);
+ return span;
+ }
+
+ private static object ProcessBreak(XElement element)
+ {
+ XElement span = null;
+ var tabWidth = (decimal?) element.Attribute(PtOpenXml.TabWidth);
+ if (tabWidth != null)
+ {
+ span = new XElement(Xhtml.span);
+ span.AddAnnotation(new Dictionary<string, string>
+ {
+ { "margin", string.Format(NumberFormatInfo.InvariantInfo, "0 0 0 {0:0.00}in", tabWidth) },
+ { "padding", "0 0 0 0" }
+ });
+ }
+
+ var paragraph = element.Ancestors(W.p).FirstOrDefault();
+ var isBidi = paragraph != null &&
+ paragraph.Elements(W.pPr).Elements(W.bidi).Any(b => b.Attribute(W.val) == null ||
+ b.Attribute(W.val).ToBoolean() == true);
+ var zeroWidthChar = isBidi ? new XEntity("#x200f") : new XEntity("#x200e");
+
+ return new object[]
+ {
+ new XElement(Xhtml.br),
+ zeroWidthChar,
+ span,
+ };
+ }
+
+ private static object ProcessContentControl(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
+ XElement element, decimal currentMarginLeft)
+ {
+ var relevantAncestors = element.Ancestors().TakeWhile(a => a.Name != W.txbxContent);
+ var isRunLevelContentControl = relevantAncestors.Any(a => a.Name == W.p);
+ if (isRunLevelContentControl)
+ {
+ return element.Elements(W.sdtContent).Elements()
+ .Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft))
+ .ToList();
+ }
+ return CreateBorderDivs(wordDoc, settings, element.Elements(W.sdtContent).Elements());
+ }
+
+ // Transform the w:p element, including the following sibling w:p element(s)
+ // in case the w:p element has a style separator. The sibling(s) will be
+ // transformed to h:span elements rather than h:p elements and added to
+ // the element (e.g., h:h2) created from the w:p element having the (first)
+ // style separator (i.e., a w:specVanish element).
+ private static object ProcessParagraph(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
+ XElement element, bool suppressTrailingWhiteSpace, decimal currentMarginLeft)
+ {
+ // Ignore this paragraph if the previous paragraph has a style separator.
+ // We have already transformed this one together with the previous one.
+ var previousParagraph = element.ElementsBeforeSelf(W.p).LastOrDefault();
+ if (HasStyleSeparator(previousParagraph)) return null;
+
+ var elementName = GetParagraphElementName(element, wordDoc);
+ var isBidi = IsBidi(element);
+ var paragraph = (XElement) ConvertParagraph(wordDoc, settings, element, elementName,
+ suppressTrailingWhiteSpace, currentMarginLeft, isBidi);
+
+ // The paragraph conversion might have created empty spans.
+ // These can and should be removed because empty spans are
+ // invalid in HTML5.
+ paragraph.Elements(Xhtml.span).Where(e => e.IsEmpty).Remove();
+
+ foreach (var span in paragraph.Elements(Xhtml.span).ToList())
+ {
+ var v = span.Value;
+ if (v.Length > 0 && (char.IsWhiteSpace(v[0]) || char.IsWhiteSpace(v[v.Length - 1])) && span.Attribute(XNamespace.Xml + "space") == null)
+ span.Add(new XAttribute(XNamespace.Xml + "space", "preserve"));
+ }
+
+ while (HasStyleSeparator(element))
+ {
+ element = element.ElementsAfterSelf(W.p).FirstOrDefault();
+ if (element == null) break;
+
+ elementName = Xhtml.span;
+ isBidi = IsBidi(element);
+ var span = (XElement)ConvertParagraph(wordDoc, settings, element, elementName,
+ suppressTrailingWhiteSpace, currentMarginLeft, isBidi);
+ var v = span.Value;
+ if (v.Length > 0 && (char.IsWhiteSpace(v[0]) || char.IsWhiteSpace(v[v.Length - 1])) && span.Attribute(XNamespace.Xml + "space") == null)
+ span.Add(new XAttribute(XNamespace.Xml + "space", "preserve"));
+ paragraph.Add(span);
+ }
+
+ return paragraph;
+ }
+
+ private static object ProcessTable(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element, decimal currentMarginLeft)
+ {
+ var style = new Dictionary<string, string>();
+ style.AddIfMissing("border-collapse", "collapse");
+ style.AddIfMissing("border", "none");
+ var bidiVisual = element.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
+ var tblW = element.Elements(W.tblPr).Elements(W.tblW).FirstOrDefault();
+ if (tblW != null)
+ {
+ var type = (string)tblW.Attribute(W.type);
+ if (type != null && type == "pct")
+ {
+ var w = (int)tblW.Attribute(W._w);
+ style.AddIfMissing("width", (w / 50) + "%");
+ }
+ }
+ var tblInd = element.Elements(W.tblPr).Elements(W.tblInd).FirstOrDefault();
+ if (tblInd != null)
+ {
+ var tblIndType = (string)tblInd.Attribute(W.type);
+ if (tblIndType != null)
+ {
+ if (tblIndType == "dxa")
+ {
+ var width = (decimal?)tblInd.Attribute(W._w);
+ if (width != null)
+ {
+ style.AddIfMissing("margin-left",
+ width > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", width / 20m)
+ : "0");
+ }
+ }
+ }
+ }
+ var tableDirection = bidiVisual != null ? new XAttribute("dir", "rtl") : new XAttribute("dir", "ltr");
+ style.AddIfMissing("margin-bottom", ".001pt");
+ var table = new XElement(Xhtml.table,
+ // TODO: Revisit and make sure the omission is covered by appropriate CSS.
+ // new XAttribute("border", "1"),
+ // new XAttribute("cellspacing", 0),
+ // new XAttribute("cellpadding", 0),
+ tableDirection,
+ element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft)));
+ table.AddAnnotation(style);
+ var jc = (string)element.Elements(W.tblPr).Elements(W.jc).Attributes(W.val).FirstOrDefault() ?? "left";
+ XAttribute dir = null;
+ XAttribute jcToUse = null;
+ if (bidiVisual != null)
+ {
+ dir = new XAttribute("dir", "rtl");
+ if (jc == "left")
+ jcToUse = new XAttribute("align", "right");
+ else if (jc == "right")
+ jcToUse = new XAttribute("align", "left");
+ else if (jc == "center")
+ jcToUse = new XAttribute("align", "center");
+ }
+ else
+ {
+ jcToUse = new XAttribute("align", jc);
+ }
+ var tableDiv = new XElement(Xhtml.div,
+ dir,
+ jcToUse,
+ table);
+ return tableDiv;
+ }
+
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ private static object ProcessTableCell(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element)
+ {
+ var style = new Dictionary<string, string>();
+ XAttribute colSpan = null;
+ XAttribute rowSpan = null;
+
+ var tcPr = element.Element(W.tcPr);
+ if (tcPr != null)
+ {
+ if ((string) tcPr.Elements(W.vMerge).Attributes(W.val).FirstOrDefault() == "restart")
+ {
+ var currentRow = element.Parent.ElementsBeforeSelf(W.tr).Count();
+ var currentCell = element.ElementsBeforeSelf(W.tc).Count();
+ var tbl = element.Parent.Parent;
+ int rowSpanCount = 1;
+ currentRow += 1;
+ while (true)
+ {
+ var row = tbl.Elements(W.tr).Skip(currentRow).FirstOrDefault();
+ if (row == null)
+ break;
+ var cell2 = row.Elements(W.tc).Skip(currentCell).FirstOrDefault();
+ if (cell2 == null)
+ break;
+ if (cell2.Elements(W.tcPr).Elements(W.vMerge).FirstOrDefault() == null)
+ break;
+ if ((string) cell2.Elements(W.tcPr).Elements(W.vMerge).Attributes(W.val).FirstOrDefault() == "restart")
+ break;
+ currentRow += 1;
+ rowSpanCount += 1;
+ }
+ rowSpan = new XAttribute("rowspan", rowSpanCount);
+ }
+
+ if (tcPr.Element(W.vMerge) != null &&
+ (string) tcPr.Elements(W.vMerge).Attributes(W.val).FirstOrDefault() != "restart")
+ return null;
+
+ if (tcPr.Element(W.vAlign) != null)
+ {
+ var vAlignVal = (string) tcPr.Elements(W.vAlign).Attributes(W.val).FirstOrDefault();
+ if (vAlignVal == "top")
+ style.AddIfMissing("vertical-align", "top");
+ else if (vAlignVal == "center")
+ style.AddIfMissing("vertical-align", "middle");
+ else if (vAlignVal == "bottom")
+ style.AddIfMissing("vertical-align", "bottom");
+ else
+ style.AddIfMissing("vertical-align", "middle");
+ }
+ style.AddIfMissing("vertical-align", "top");
+
+ if ((string) tcPr.Elements(W.tcW).Attributes(W.type).FirstOrDefault() == "dxa")
+ {
+ decimal width = (int) tcPr.Elements(W.tcW).Attributes(W._w).FirstOrDefault();
+ style.AddIfMissing("width", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", width/20m));
+ }
+ if ((string) tcPr.Elements(W.tcW).Attributes(W.type).FirstOrDefault() == "pct")
+ {
+ decimal width = (int) tcPr.Elements(W.tcW).Attributes(W._w).FirstOrDefault();
+ style.AddIfMissing("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}%", width/50m));
+ }
+
+ var tcBorders = tcPr.Element(W.tcBorders);
+ GenerateBorderStyle(tcBorders, W.top, style, BorderType.Cell);
+ GenerateBorderStyle(tcBorders, W.right, style, BorderType.Cell);
+ GenerateBorderStyle(tcBorders, W.bottom, style, BorderType.Cell);
+ GenerateBorderStyle(tcBorders, W.left, style, BorderType.Cell);
+
+ CreateStyleFromShd(style, tcPr.Element(W.shd));
+
+ var gridSpan = tcPr.Elements(W.gridSpan).Attributes(W.val).Select(a => (int?) a).FirstOrDefault();
+ if (gridSpan != null)
+ colSpan = new XAttribute("colspan", (int) gridSpan);
+ }
+ style.AddIfMissing("padding-top", "0");
+ style.AddIfMissing("padding-bottom", "0");
+
+ var cell = new XElement(Xhtml.td,
+ rowSpan,
+ colSpan,
+ CreateBorderDivs(wordDoc, settings, element.Elements()));
+ cell.AddAnnotation(style);
+ return cell;
+ }
+
+ private static object ProcessTableRow(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element,
+ decimal currentMarginLeft)
+ {
+ var style = new Dictionary<string, string>();
+ int? trHeight = (int?) element.Elements(W.trPr).Elements(W.trHeight).Attributes(W.val).FirstOrDefault();
+ if (trHeight != null)
+ style.AddIfMissing("height",
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", (decimal) trHeight/1440m));
+ var htmlRow = new XElement(Xhtml.tr,
+ element.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft)));
+ if (style.Any())
+ htmlRow.AddAnnotation(style);
+ return htmlRow;
+ }
+
+ private static bool HasStyleSeparator(XElement element)
+ {
+ return element != null && element.Elements(W.pPr).Elements(W.rPr).Any(e => GetBoolProp(e, W.specVanish));
+ }
+
+ private static bool IsBidi(XElement element)
+ {
+ return element
+ .Elements(W.pPr)
+ .Elements(W.bidi)
+ .Any(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true);
+ }
+
+ private static XName GetParagraphElementName(XElement element, WordprocessingDocument wordDoc)
+ {
+ var elementName = Xhtml.p;
+
+ var styleId = (string) element.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
+ if (styleId == null) return elementName;
+
+ var style = GetStyle(styleId, wordDoc);
+ if (style == null) return elementName;
+
+ var outlineLevel =
+ (int?) style.Elements(W.pPr).Elements(W.outlineLvl).Attributes(W.val).FirstOrDefault();
+ if (outlineLevel != null && outlineLevel <= 5)
+ {
+ elementName = Xhtml.xhtml + string.Format("h{0}", outlineLevel + 1);
+ }
+
+ return elementName;
+ }
+
+ private static XElement GetStyle(string styleId, WordprocessingDocument wordDoc)
+ {
+ var stylesPart = wordDoc.MainDocumentPart.StyleDefinitionsPart;
+ if (stylesPart == null) return null;
+
+ var styles = stylesPart.GetXDocument().Root;
+ return styles != null
+ ? styles.Elements(W.style).FirstOrDefault(s => (string) s.Attribute(W.styleId) == styleId)
+ : null;
+ }
+
+ private static object CreateSectionDivs(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement element)
+ {
+ // note: when building a paging html converter, need to attend to new sections with page breaks here.
+ // This code conflates adjacent sections if they have identical formatting, which is not an issue
+ // for the non-paging transform.
+ var groupedIntoDivs = element
+ .Elements()
+ .GroupAdjacent(e => {
+ var sectAnnotation = e.Annotation<SectionAnnotation>();
+ return sectAnnotation != null ? sectAnnotation.SectionElement.ToString() : "";
+ });
+
+ // note: when creating a paging html converter, need to pay attention to w:rtlGutter element.
+ var divList = groupedIntoDivs
+ .Select(g =>
+ {
+ var sectPr = g.First().Annotation<SectionAnnotation>();
+ XElement bidi = null;
+ if (sectPr != null)
+ {
+ bidi = sectPr
+ .SectionElement
+ .Elements(W.bidi)
+ .FirstOrDefault(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true);
+ }
+ if (sectPr == null || bidi == null)
+ {
+ var div = new XElement(Xhtml.div, CreateBorderDivs(wordDoc, settings, g));
+ return div;
+ }
+ else
+ {
+ var div = new XElement(Xhtml.div,
+ new XAttribute("dir", "rtl"),
+ CreateBorderDivs(wordDoc, settings, g));
+ return div;
+ }
+ });
+ return divList;
+ }
+
+ private enum BorderType
+ {
+ Paragraph,
+ Cell,
+ };
+
+ /*
+ * Notes on line spacing
+ *
+ * the w:line and w:lineRule attributes control spacing between lines - including between lines within a paragraph
+ *
+ * If w:spacing w:lineRule="auto" then
+ * w:spacing w:line is a percentage where 240 == 100%
+ *
+ * (line value / 240) * 100 = percentage of line
+ *
+ * If w:spacing w:lineRule="exact" or w:lineRule="atLeast" then
+ * w:spacing w:line is in twips
+ * 1440 = exactly one inch from line to line
+ *
+ * Handle
+ * - ind
+ * - jc
+ * - numPr
+ * - pBdr
+ * - shd
+ * - spacing
+ * - textAlignment
+ *
+ * Don't Handle (yet)
+ * - adjustRightInd?
+ * - autoSpaceDE
+ * - autoSpaceDN
+ * - bidi
+ * - contextualSpacing
+ * - divId
+ * - framePr
+ * - keepLines
+ * - keepNext
+ * - kinsoku
+ * - mirrorIndents
+ * - overflowPunct
+ * - pageBreakBefore
+ * - snapToGrid
+ * - suppressAutoHyphens
+ * - suppressLineNumbers
+ * - suppressOverlap
+ * - tabs
+ * - textBoxTightWrap
+ * - textDirection
+ * - topLinePunct
+ * - widowControl
+ * - wordWrap
+ *
+ */
+
+ private static object ConvertParagraph(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
+ XElement paragraph, XName elementName, bool suppressTrailingWhiteSpace, decimal currentMarginLeft, bool isBidi)
+ {
+ var style = DefineParagraphStyle(paragraph, elementName, suppressTrailingWhiteSpace, currentMarginLeft, isBidi);
+ var rtl = isBidi ? new XAttribute("dir", "rtl") : new XAttribute("dir", "ltr");
+ var firstMark = isBidi ? new XEntity("#x200f") : null;
+
+ // Analyze initial runs to see whether we have a tab, in which case we will render
+ // a span with a defined width and ignore the tab rather than rendering the text
+ // preceding the tab and the tab as a span with a computed width.
+ var firstTabRun = paragraph
+ .Elements(W.r)
+ .FirstOrDefault(run => run.Elements(W.tab).Any());
+ var elementsPrecedingTab = firstTabRun != null
+ ? paragraph.Elements(W.r).TakeWhile(e => e != firstTabRun)
+ .Where(e => e.Elements().Any(c => c.Attributes(PtOpenXml.TabWidth).Any())).ToList()
+ : Enumerable.Empty<XElement>().ToList();
+
+ // TODO: Revisit
+ // For the time being, if a hyperlink field precedes the tab, we'll render it as before.
+ var hyperlinkPrecedesTab = elementsPrecedingTab
+ .Elements(W.r)
+ .Elements(W.instrText)
+ .Select(e => e.Value)
+ .Any(value => value != null && value.TrimStart().ToUpper().StartsWith("HYPERLINK"));
+ if (hyperlinkPrecedesTab)
+ {
+ var paraElement1 = new XElement(elementName,
+ rtl,
+ firstMark,
+ ConvertContentThatCanContainFields(wordDoc, settings, paragraph.Elements()));
+ paraElement1.AddAnnotation(style);
+ return paraElement1;
+ }
+
+ var txElementsPrecedingTab = TransformElementsPrecedingTab(wordDoc, settings, elementsPrecedingTab, firstTabRun);
+ var elementsSucceedingTab = firstTabRun != null
+ ? paragraph.Elements().SkipWhile(e => e != firstTabRun).Skip(1)
+ : paragraph.Elements();
+ var paraElement = new XElement(elementName,
+ rtl,
+ firstMark,
+ txElementsPrecedingTab,
+ ConvertContentThatCanContainFields(wordDoc, settings, elementsSucceedingTab));
+ paraElement.AddAnnotation(style);
+
+ return paraElement;
+ }
+
+ private static List<object> TransformElementsPrecedingTab(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
+ List<XElement> elementsPrecedingTab, XElement firstTabRun)
+ {
+ var tabWidth = firstTabRun != null
+ ? (decimal?) firstTabRun.Elements(W.tab).Attributes(PtOpenXml.TabWidth).FirstOrDefault() ?? 0m
+ : 0m;
+ var precedingElementsWidth = elementsPrecedingTab
+ .Elements()
+ .Where(c => c.Attributes(PtOpenXml.TabWidth).Any())
+ .Select(e => (decimal) e.Attribute(PtOpenXml.TabWidth))
+ .Sum();
+ var totalWidth = precedingElementsWidth + tabWidth;
+
+ var txElementsPrecedingTab = elementsPrecedingTab
+ .Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, 0m))
+ .ToList();
+ if (txElementsPrecedingTab.Count > 1)
+ {
+ var span = new XElement(Xhtml.span, txElementsPrecedingTab);
+ var spanStyle = new Dictionary<string, string>
+ {
+ { "display", "inline-block" },
+ { "text-indent", "0" },
+ { "width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}in", totalWidth) }
+ };
+ span.AddAnnotation(spanStyle);
+ }
+ else if (txElementsPrecedingTab.Count == 1)
+ {
+ var element = txElementsPrecedingTab.First() as XElement;
+ if (element != null)
+ {
+ var spanStyle = element.Annotation<Dictionary<string, string>>();
+ spanStyle.AddIfMissing("display", "inline-block");
+ spanStyle.AddIfMissing("text-indent", "0");
+ spanStyle.AddIfMissing("width", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}in", totalWidth));
+ }
+ }
+ return txElementsPrecedingTab;
+ }
+
+ private static Dictionary<string, string> DefineParagraphStyle(XElement paragraph, XName elementName,
+ bool suppressTrailingWhiteSpace, decimal currentMarginLeft, bool isBidi)
+ {
+ var style = new Dictionary<string, string>();
+
+ var styleName = (string) paragraph.Attribute(PtOpenXml.StyleName);
+ if (styleName != null)
+ style.Add("PtStyleName", styleName);
+
+ var pPr = paragraph.Element(W.pPr);
+ if (pPr == null) return style;
+
+ CreateStyleFromSpacing(style, pPr.Element(W.spacing), elementName, suppressTrailingWhiteSpace);
+ CreateStyleFromInd(style, pPr.Element(W.ind), elementName, currentMarginLeft, isBidi);
+
+ // todo need to handle
+ // - both
+ // - mediumKashida
+ // - distribute
+ // - numTab
+ // - highKashida
+ // - lowKashida
+ // - thaiDistribute
+
+ CreateStyleFromJc(style, pPr.Element(W.jc), isBidi);
+ CreateStyleFromShd(style, pPr.Element(W.shd));
+
+ // Pt.FontName
+ var font = (string) paragraph.Attributes(PtOpenXml.FontName).FirstOrDefault();
+ if (font != null)
+ CreateFontCssProperty(font, style);
+
+ DefineFontSize(style, paragraph);
+ DefineLineHeight(style, paragraph);
+
+ // vertical text alignment as of December 2013 does not work in any major browsers.
+ CreateStyleFromTextAlignment(style, pPr.Element(W.textAlignment));
+
+ style.AddIfMissing("margin-top", "0");
+ style.AddIfMissing("margin-left", "0");
+ style.AddIfMissing("margin-right", "0");
+ style.AddIfMissing("margin-bottom", ".001pt");
+
+ return style;
+ }
+
+ private static void CreateStyleFromInd(Dictionary<string, string> style, XElement ind, XName elementName,
+ decimal currentMarginLeft, bool isBidi)
+ {
+ if (ind == null) return;
+
+ var left = (decimal?) ind.Attribute(W.left);
+ if (left != null && elementName != Xhtml.span)
+ {
+ var leftInInches = (decimal) left/1440 - currentMarginLeft;
+ style.AddIfMissing(isBidi ? "margin-right" : "margin-left",
+ leftInInches > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", leftInInches)
+ : "0");
+ }
+
+ var right = (decimal?) ind.Attribute(W.right);
+ if (right != null)
+ {
+ var rightInInches = (decimal) right/1440;
+ style.AddIfMissing(isBidi ? "margin-left" : "margin-right",
+ rightInInches > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", rightInInches)
+ : "0");
+ }
+
+ var firstLine = (decimal?) ind.Attribute(W.firstLine);
+ if (firstLine != null && elementName != Xhtml.span)
+ {
+ var firstLineInInches = (decimal) firstLine/1440m;
+ style.AddIfMissing("text-indent",
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", firstLineInInches));
+ }
+
+ var hanging = (decimal?) ind.Attribute(W.hanging);
+ if (hanging != null && elementName != Xhtml.span)
+ {
+ var hangingInInches = (decimal) -hanging/1440m;
+ style.AddIfMissing("text-indent",
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", hangingInInches));
+ }
+ }
+
+ private static void CreateStyleFromJc(Dictionary<string, string> style, XElement jc, bool isBidi)
+ {
+ if (jc != null)
+ {
+ var jcVal = (string)jc.Attributes(W.val).FirstOrDefault() ?? "left";
+ if (jcVal == "left")
+ style.AddIfMissing("text-align", isBidi ? "right" : "left");
+ else if (jcVal == "right")
+ style.AddIfMissing("text-align", isBidi ? "left" : "right");
+ else if (jcVal == "center")
+ style.AddIfMissing("text-align", "center");
+ else if (jcVal == "both")
+ style.AddIfMissing("text-align", "justify");
+ }
+ }
+
+ private static void CreateStyleFromSpacing(Dictionary<string, string> style, XElement spacing, XName elementName,
+ bool suppressTrailingWhiteSpace)
+ {
+ if (spacing == null) return;
+
+ var spacingBefore = (decimal?) spacing.Attribute(W.before);
+ if (spacingBefore != null && elementName != Xhtml.span)
+ style.AddIfMissing("margin-top",
+ spacingBefore > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", spacingBefore/20.0m)
+ : "0");
+
+ var lineRule = (string) spacing.Attribute(W.lineRule);
+ if (lineRule == "auto")
+ {
+ var line = (decimal) spacing.Attribute(W.line);
+ if (line != 240m)
+ {
+ var pct = (line/240m)*100m;
+ style.Add("line-height", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}%", pct));
+ }
+ }
+ if (lineRule == "exact")
+ {
+ var line = (decimal) spacing.Attribute(W.line);
+ var points = line/20m;
+ style.Add("line-height", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", points));
+ }
+ if (lineRule == "atLeast")
+ {
+ var line = (decimal) spacing.Attribute(W.line);
+ var points = line/20m;
+ if (points >= 14m)
+ style.Add("line-height", string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", points));
+ }
+
+ var spacingAfter = suppressTrailingWhiteSpace ? 0m : (decimal?) spacing.Attribute(W.after);
+ if (spacingAfter != null)
+ style.AddIfMissing("margin-bottom",
+ spacingAfter > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", spacingAfter/20.0m)
+ : "0");
+ }
+
+ private static void CreateStyleFromTextAlignment(Dictionary<string, string> style, XElement textAlignment)
+ {
+ if (textAlignment == null) return;
+
+ var verticalTextAlignment = (string)textAlignment.Attributes(W.val).FirstOrDefault();
+ if (verticalTextAlignment == null || verticalTextAlignment == "auto") return;
+
+ if (verticalTextAlignment == "top")
+ style.AddIfMissing("vertical-align", "top");
+ else if (verticalTextAlignment == "center")
+ style.AddIfMissing("vertical-align", "middle");
+ else if (verticalTextAlignment == "baseline")
+ style.AddIfMissing("vertical-align", "baseline");
+ else if (verticalTextAlignment == "bottom")
+ style.AddIfMissing("vertical-align", "bottom");
+ }
+
+ private static void DefineFontSize(Dictionary<string, string> style, XElement paragraph)
+ {
+ var sz = paragraph
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.r)
+ .Select(r => GetFontSize(r))
+ .Max();
+ if (sz != null)
+ style.AddIfMissing("font-size", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", sz / 2.0m));
+ }
+
+ private static void DefineLineHeight(Dictionary<string, string> style, XElement paragraph)
+ {
+ var allRunsAreUniDirectional = paragraph
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.r)
+ .Select(run => (string)run.Attribute(PtOpenXml.LanguageType))
+ .All(lt => lt != "bidi");
+ if (allRunsAreUniDirectional)
+ style.AddIfMissing("line-height", "108%");
+ }
+
+ /*
+ * Handle:
+ * - b
+ * - bdr
+ * - caps
+ * - color
+ * - dstrike
+ * - highlight
+ * - i
+ * - position
+ * - rFonts
+ * - shd
+ * - smallCaps
+ * - spacing
+ * - strike
+ * - sz
+ * - u
+ * - vanish
+ * - vertAlign
+ *
+ * Don't handle:
+ * - em
+ * - emboss
+ * - fitText
+ * - imprint
+ * - kern
+ * - outline
+ * - shadow
+ * - w
+ *
+ */
+
+ private static object ConvertRun(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, XElement run)
+ {
+ var rPr = run.Element(W.rPr);
+ if (rPr == null)
+ return run.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, 0m));
+
+ // hide all content that contains the w:rPr/w:webHidden element
+ if (rPr.Element(W.webHidden) != null)
+ return null;
+
+ var style = DefineRunStyle(run);
+ object content = run.Elements().Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, 0m));
+
+ // Wrap content in h:sup or h:sub elements as necessary.
+ if (rPr.Element(W.vertAlign) != null)
+ {
+ XElement newContent = null;
+ var vertAlignVal = (string)rPr.Elements(W.vertAlign).Attributes(W.val).FirstOrDefault();
+ switch (vertAlignVal)
+ {
+ case "superscript":
+ newContent = new XElement(Xhtml.sup, content);
+ break;
+ case "subscript":
+ newContent = new XElement(Xhtml.sub, content);
+ break;
+ }
+ if (newContent != null && newContent.Nodes().Any())
+ content = newContent;
+ }
+
+ var langAttribute = GetLangAttribute(run);
+
+ XEntity runStartMark;
+ XEntity runEndMark;
+ DetermineRunMarks(run, rPr, style, out runStartMark, out runEndMark);
+
+ if (style.Any() || langAttribute != null || runStartMark != null)
+ {
+ style.AddIfMissing("margin", "0");
+ style.AddIfMissing("padding", "0");
+ var xe = new XElement(Xhtml.span,
+ langAttribute,
+ runStartMark,
+ content,
+ runEndMark);
+
+ xe.AddAnnotation(style);
+ content = xe;
+ }
+ return content;
+ }
+
+ [SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
+ private static Dictionary<string, string> DefineRunStyle(XElement run)
+ {
+ var style = new Dictionary<string, string>();
+
+ var rPr = run.Elements(W.rPr).First();
+
+ var styleName = (string) run.Attribute(PtOpenXml.StyleName);
+ if (styleName != null)
+ style.Add("PtStyleName", styleName);
+
+ // W.bdr
+ if (rPr.Element(W.bdr) != null && (string) rPr.Elements(W.bdr).Attributes(W.val).FirstOrDefault() != "none")
+ {
+ style.AddIfMissing("border", "solid windowtext 1.0pt");
+ style.AddIfMissing("padding", "0");
+ }
+
+ // W.color
+ var color = (string) rPr.Elements(W.color).Attributes(W.val).FirstOrDefault();
+ if (color != null)
+ CreateColorProperty("color", color, style);
+
+ // W.highlight
+ var highlight = (string) rPr.Elements(W.highlight).Attributes(W.val).FirstOrDefault();
+ if (highlight != null)
+ CreateColorProperty("background", highlight, style);
+
+ // W.shd
+ var shade = (string) rPr.Elements(W.shd).Attributes(W.fill).FirstOrDefault();
+ if (shade != null)
+ CreateColorProperty("background", shade, style);
+
+ // Pt.FontName
+ var sym = run.Element(W.sym);
+ var font = sym != null
+ ? (string) sym.Attributes(W.font).FirstOrDefault()
+ : (string) run.Attributes(PtOpenXml.FontName).FirstOrDefault();
+ if (font != null)
+ CreateFontCssProperty(font, style);
+
+ // W.sz
+ var languageType = (string)run.Attribute(PtOpenXml.LanguageType);
+ var sz = GetFontSize(languageType, rPr);
+ if (sz != null)
+ style.AddIfMissing("font-size", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", sz/2.0m));
+
+ // W.caps
+ if (GetBoolProp(rPr, W.caps))
+ style.AddIfMissing("text-transform", "uppercase");
+
+ // W.smallCaps
+ if (GetBoolProp(rPr, W.smallCaps))
+ style.AddIfMissing("font-variant", "small-caps");
+
+ // W.spacing
+ var spacingInTwips = (decimal?) rPr.Elements(W.spacing).Attributes(W.val).FirstOrDefault();
+ if (spacingInTwips != null)
+ style.AddIfMissing("letter-spacing",
+ spacingInTwips > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", spacingInTwips/20)
+ : "0");
+
+ // W.position
+ var position = (decimal?) rPr.Elements(W.position).Attributes(W.val).FirstOrDefault();
+ if (position != null)
+ {
+ style.AddIfMissing("position", "relative");
+ style.AddIfMissing("top", string.Format(NumberFormatInfo.InvariantInfo, "{0}pt", -(position/2)));
+ }
+
+ // W.vanish
+ if (GetBoolProp(rPr, W.vanish) && !GetBoolProp(rPr, W.specVanish))
+ style.AddIfMissing("display", "none");
+
+ // W.u
+ if (rPr.Element(W.u) != null && (string) rPr.Elements(W.u).Attributes(W.val).FirstOrDefault() != "none")
+ style.AddIfMissing("text-decoration", "underline");
+
+ // W.i
+ style.AddIfMissing("font-style", GetBoolProp(rPr, W.i) ? "italic" : "normal");
+
+ // W.b
+ style.AddIfMissing("font-weight", GetBoolProp(rPr, W.b) ? "bold" : "normal");
+
+ // W.strike
+ if (GetBoolProp(rPr, W.strike) || GetBoolProp(rPr, W.dstrike))
+ style.AddIfMissing("text-decoration", "line-through");
+
+ return style;
+ }
+
+ private static decimal? GetFontSize(XElement e)
+ {
+ var languageType = (string)e.Attribute(PtOpenXml.LanguageType);
+ if (e.Name == W.p)
+ {
+ return GetFontSize(languageType, e.Elements(W.pPr).Elements(W.rPr).FirstOrDefault());
+ }
+ if (e.Name == W.r)
+ {
+ return GetFontSize(languageType, e.Element(W.rPr));
+ }
+ return null;
+ }
+
+ private static decimal? GetFontSize(string languageType, XElement rPr)
+ {
+ if (rPr == null) return null;
+ return languageType == "bidi"
+ ? (decimal?) rPr.Elements(W.szCs).Attributes(W.val).FirstOrDefault()
+ : (decimal?) rPr.Elements(W.sz).Attributes(W.val).FirstOrDefault();
+ }
+
+ private static void DetermineRunMarks(XElement run, XElement rPr, Dictionary<string, string> style, out XEntity runStartMark, out XEntity runEndMark)
+ {
+ runStartMark = null;
+ runEndMark = null;
+
+ // Only do the following for text runs.
+ if (run.Element(W.t) == null) return;
+
+ // Can't add directional marks if the font-family is symbol - they are visible, and display as a ?
+ var addDirectionalMarks = true;
+ if (style.ContainsKey("font-family"))
+ {
+ if (style["font-family"].ToLower() == "symbol")
+ addDirectionalMarks = false;
+ }
+ if (!addDirectionalMarks) return;
+
+ var isRtl = rPr.Element(W.rtl) != null;
+ if (isRtl)
+ {
+ runStartMark = new XEntity("#x200f"); // RLM
+ runEndMark = new XEntity("#x200f"); // RLM
+ }
+ else
+ {
+ var paragraph = run.Ancestors(W.p).First();
+ var paraIsBidi = paragraph
+ .Elements(W.pPr)
+ .Elements(W.bidi)
+ .Any(b => b.Attribute(W.val) == null || b.Attribute(W.val).ToBoolean() == true);
+
+ if (paraIsBidi)
+ {
+ runStartMark = new XEntity("#x200e"); // LRM
+ runEndMark = new XEntity("#x200e"); // LRM
+ }
+ }
+ }
+
+ private static XAttribute GetLangAttribute(XElement run)
+ {
+ const string defaultLanguage = "en-US"; // todo need to get defaultLanguage
+
+ var rPr = run.Elements(W.rPr).First();
+ var languageType = (string)run.Attribute(PtOpenXml.LanguageType);
+
+ string lang = null;
+ if (languageType == "western")
+ lang = (string) rPr.Elements(W.lang).Attributes(W.val).FirstOrDefault();
+ else if (languageType == "bidi")
+ lang = (string) rPr.Elements(W.lang).Attributes(W.bidi).FirstOrDefault();
+ else if (languageType == "eastAsia")
+ lang = (string) rPr.Elements(W.lang).Attributes(W.eastAsia).FirstOrDefault();
+ if (lang == null)
+ lang = defaultLanguage;
+
+ return lang != defaultLanguage ? new XAttribute("lang", lang) : null;
+ }
+
+ private static void AdjustTableBorders(WordprocessingDocument wordDoc)
+ {
+ // Note: when implementing a paging version of the HTML transform, this needs to be done
+ // for all content parts, not just the main document part.
+
+ var xd = wordDoc.MainDocumentPart.GetXDocument();
+ foreach (var tbl in xd.Descendants(W.tbl))
+ AdjustTableBorders(tbl);
+ wordDoc.MainDocumentPart.PutXDocument();
+ }
+
+ private static void AdjustTableBorders(XElement tbl)
+ {
+ var ta = tbl
+ .Elements(W.tr)
+ .Select(r => r
+ .Elements(W.tc)
+ .SelectMany(c =>
+ Enumerable.Repeat(c,
+ (int?) c.Elements(W.tcPr).Elements(W.gridSpan).Attributes(W.val).FirstOrDefault() ?? 1))
+ .ToArray())
+ .ToArray();
+
+ for (var y = 0; y < ta.Length; y++)
+ {
+ for (var x = 0; x < ta[y].Length; x++)
+ {
+ var thisCell = ta[y][x];
+ FixTopBorder(ta, thisCell, x, y);
+ FixLeftBorder(ta, thisCell, x, y);
+ FixBottomBorder(ta, thisCell, x, y);
+ FixRightBorder(ta, thisCell, x, y);
+ }
+ }
+ }
+
+ private static void FixTopBorder(XElement[][] ta, XElement thisCell, int x, int y)
+ {
+ if (y > 0)
+ {
+ var rowAbove = ta[y - 1];
+ if (x < rowAbove.Length - 1)
+ {
+ XElement cellAbove = ta[y - 1][x];
+ if (cellAbove != null &&
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
+ cellAbove.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
+ {
+ ResolveCellBorder(
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.top).FirstOrDefault(),
+ cellAbove.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.bottom).FirstOrDefault());
+ }
+ }
+ }
+ }
+
+ private static void FixLeftBorder(XElement[][] ta, XElement thisCell, int x, int y)
+ {
+ if (x > 0)
+ {
+ XElement cellLeft = ta[y][x - 1];
+ if (cellLeft != null &&
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
+ cellLeft.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
+ {
+ ResolveCellBorder(
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.left).FirstOrDefault(),
+ cellLeft.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.right).FirstOrDefault());
+ }
+ }
+ }
+
+ private static void FixBottomBorder(XElement[][] ta, XElement thisCell, int x, int y)
+ {
+ if (y < ta.Length - 1)
+ {
+ var rowBelow = ta[y + 1];
+ if (x < rowBelow.Length - 1)
+ {
+ XElement cellBelow = ta[y + 1][x];
+ if (cellBelow != null &&
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
+ cellBelow.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
+ {
+ ResolveCellBorder(
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.bottom).FirstOrDefault(),
+ cellBelow.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.top).FirstOrDefault());
+ }
+ }
+ }
+ }
+
+ private static void FixRightBorder(XElement[][] ta, XElement thisCell, int x, int y)
+ {
+ if (x < ta[y].Length - 1)
+ {
+ XElement cellRight = ta[y][x + 1];
+ if (cellRight != null &&
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null &&
+ cellRight.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault() != null)
+ {
+ ResolveCellBorder(
+ thisCell.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.right).FirstOrDefault(),
+ cellRight.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.left).FirstOrDefault());
+ }
+ }
+ }
+
+ private static readonly Dictionary<string, int> BorderTypePriority = new Dictionary<string, int>()
+ {
+ { "single", 1 },
+ { "thick", 2 },
+ { "double", 3 },
+ { "dotted", 4 },
+ };
+
+ private static readonly Dictionary<string, int> BorderNumber = new Dictionary<string, int>()
+ {
+ {"single", 1 },
+ {"thick", 2 },
+ {"double", 3 },
+ {"dotted", 4 },
+ {"dashed", 5 },
+ {"dotDash", 5 },
+ {"dotDotDash", 5 },
+ {"triple", 6 },
+ {"thinThickSmallGap", 6 },
+ {"thickThinSmallGap", 6 },
+ {"thinThickThinSmallGap", 6 },
+ {"thinThickMediumGap", 6 },
+ {"thickThinMediumGap", 6 },
+ {"thinThickThinMediumGap", 6 },
+ {"thinThickLargeGap", 6 },
+ {"thickThinLargeGap", 6 },
+ {"thinThickThinLargeGap", 6 },
+ {"wave", 7 },
+ {"doubleWave", 7 },
+ {"dashSmallGap", 5 },
+ {"dashDotStroked", 5 },
+ {"threeDEmboss", 7 },
+ {"threeDEngrave", 7 },
+ {"outset", 7 },
+ {"inset", 7 },
+ };
+
+ private static void ResolveCellBorder(XElement border1, XElement border2)
+ {
+ if (border1 == null || border2 == null)
+ return;
+ if ((string)border1.Attribute(W.val) == "nil" || (string)border2.Attribute(W.val) == "nil")
+ return;
+ if ((string)border1.Attribute(W.sz) == "nil" || (string)border2.Attribute(W.sz) == "nil")
+ return;
+
+ var border1Val = (string)border1.Attribute(W.val);
+ var border1Weight = 1;
+ if (BorderNumber.ContainsKey(border1Val))
+ border1Weight = BorderNumber[border1Val];
+
+ var border2Val = (string)border2.Attribute(W.val);
+ var border2Weight = 1;
+ if (BorderNumber.ContainsKey(border2Val))
+ border2Weight = BorderNumber[border2Val];
+
+ if (border1Weight != border2Weight)
+ {
+ if (border1Weight < border2Weight)
+ BorderOverride(border2, border1);
+ else
+ BorderOverride(border1, border2);
+ }
+
+ if ((decimal)border1.Attribute(W.sz) > (decimal)border2.Attribute(W.sz))
+ {
+ BorderOverride(border1, border2);
+ return;
+ }
+
+ if ((decimal)border1.Attribute(W.sz) < (decimal)border2.Attribute(W.sz))
+ {
+ BorderOverride(border2, border1);
+ return;
+ }
+
+ var border1Type = (string)border1.Attribute(W.val);
+ var border2Type = (string)border2.Attribute(W.val);
+ if (BorderTypePriority.ContainsKey(border1Type) &&
+ BorderTypePriority.ContainsKey(border2Type))
+ {
+ var border1Pri = BorderTypePriority[border1Type];
+ var border2Pri = BorderTypePriority[border2Type];
+ if (border1Pri < border2Pri)
+ {
+ BorderOverride(border2, border1);
+ return;
+ }
+ if (border2Pri < border1Pri)
+ {
+ BorderOverride(border1, border2);
+ return;
+ }
+ }
+
+ var color1Str = (string)border1.Attribute(W.color);
+ if (color1Str == "auto")
+ color1Str = "000000";
+ var color2Str = (string)border2.Attribute(W.color);
+ if (color2Str == "auto")
+ color2Str = "000000";
+ if (color1Str != null && color2Str != null && color1Str != color2Str)
+ {
+ try
+ {
+ var color1 = Convert.ToInt32(color1Str, 16);
+ var color2 = Convert.ToInt32(color2Str, 16);
+ if (color1 < color2)
+ {
+ BorderOverride(border1, border2);
+ return;
+ }
+ if (color2 < color1)
+ {
+ BorderOverride(border2, border1);
+ }
+ }
+ // if the above throws ArgumentException, FormatException, or OverflowException, then abort
+ catch (Exception)
+ {
+ // Ignore
+ }
+ }
+ }
+
+ private static void BorderOverride(XElement fromBorder, XElement toBorder)
+ {
+ toBorder.Attribute(W.val).Value = fromBorder.Attribute(W.val).Value;
+ if (fromBorder.Attribute(W.color) != null)
+ toBorder.SetAttributeValue(W.color, fromBorder.Attribute(W.color).Value);
+ if (fromBorder.Attribute(W.sz) != null)
+ toBorder.SetAttributeValue(W.sz, fromBorder.Attribute(W.sz).Value);
+ if (fromBorder.Attribute(W.themeColor) != null)
+ toBorder.SetAttributeValue(W.themeColor, fromBorder.Attribute(W.themeColor).Value);
+ if (fromBorder.Attribute(W.themeTint) != null)
+ toBorder.SetAttributeValue(W.themeTint, fromBorder.Attribute(W.themeTint).Value);
+ }
+
+ private static void CalculateSpanWidthForTabs(WordprocessingDocument wordDoc)
+ {
+ // Note: when implementing a paging version of the HTML transform, this needs to be done
+ // for all content parts, not just the main document part.
+
+ // w:defaultTabStop in settings
+ var sxd = wordDoc.MainDocumentPart.DocumentSettingsPart.GetXDocument();
+ var defaultTabStop = (int?)sxd.Descendants(W.defaultTabStop).Attributes(W.val).FirstOrDefault() ?? 720;
+
+ var pxd = wordDoc.MainDocumentPart.GetXDocument();
+ var root = pxd.Root;
+ if (root == null) return;
+
+ var newRoot = (XElement)CalculateSpanWidthTransform(root, defaultTabStop);
+ root.ReplaceWith(newRoot);
+ wordDoc.MainDocumentPart.PutXDocument();
+ }
+
+ // TODO: Refactor. This method is way too long.
+ [SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
+ private static object CalculateSpanWidthTransform(XNode node, int defaultTabStop)
+ {
+ var element = node as XElement;
+ if (element == null) return node;
+
+ // if it is not a paragraph or if there are no tabs in the paragraph,
+ // then no need to continue processing.
+ if (element.Name != W.p ||
+ !element.DescendantsTrimmed(W.txbxContent).Where(d => d.Name == W.r).Elements(W.tab).Any())
+ {
+ // TODO: Revisit. Can we just return the node if it is a paragraph that does not have any tab?
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => CalculateSpanWidthTransform(n, defaultTabStop)));
+ }
+
+ var clonedPara = new XElement(element);
+
+ var leftInTwips = 0;
+ var firstInTwips = 0;
+
+ var ind = clonedPara.Elements(W.pPr).Elements(W.ind).FirstOrDefault();
+ if (ind != null)
+ {
+ // todo need to handle start and end attributes
+
+ var left = (int?)ind.Attribute(W.left);
+ if (left != null)
+ leftInTwips = (int)left;
+
+ var firstLine = 0;
+ var firstLineAtt = (int?)ind.Attribute(W.firstLine);
+ if (firstLineAtt != null)
+ firstLine = (int)firstLineAtt;
+
+ var hangingAtt = (int?)ind.Attribute(W.hanging);
+ if (hangingAtt != null)
+ firstLine = -(int)hangingAtt;
+
+ firstInTwips = leftInTwips + firstLine;
+ }
+
+ // calculate the tab stops, in twips
+ var tabs = clonedPara
+ .Elements(W.pPr)
+ .Elements(W.tabs)
+ .FirstOrDefault();
+
+ if (tabs == null)
+ {
+ if (leftInTwips == 0)
+ {
+ tabs = new XElement(W.tabs,
+ Enumerable.Range(1, 100)
+ .Select(r => new XElement(W.tab,
+ new XAttribute(W.val, "left"),
+ new XAttribute(W.pos, r * defaultTabStop))));
+ }
+ else
+ {
+ tabs = new XElement(W.tabs,
+ new XElement(W.tab,
+ new XAttribute(W.val, "left"),
+ new XAttribute(W.pos, leftInTwips)));
+ tabs = AddDefaultTabsAfterLastTab(tabs, defaultTabStop);
+ }
+ }
+ else
+ {
+ if (leftInTwips != 0)
+ {
+ tabs.Add(
+ new XElement(W.tab,
+ new XAttribute(W.val, "left"),
+ new XAttribute(W.pos, leftInTwips)));
+ }
+ tabs = AddDefaultTabsAfterLastTab(tabs, defaultTabStop);
+ }
+
+ var twipCounter = firstInTwips;
+ var contentToMeasure = element.DescendantsTrimmed(z => z.Name == W.txbxContent || z.Name == W.pPr || z.Name == W.rPr).ToArray();
+ var currentElementIdx = 0;
+ while (true)
+ {
+ var currentElement = contentToMeasure[currentElementIdx];
+
+ if (currentElement.Name == W.br)
+ {
+ twipCounter = leftInTwips;
+
+ currentElement.Add(new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo,
+ "{0:0.000}", (decimal)firstInTwips / 1440m)));
+
+ currentElementIdx++;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+ }
+
+ if (currentElement.Name == W.tab)
+ {
+ var runContainingTabToReplace = currentElement.Parent;
+ var fontNameAtt = runContainingTabToReplace.Attribute(PtOpenXml.pt + "FontName") ??
+ runContainingTabToReplace.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
+
+ var testAmount = twipCounter;
+
+ var tabAfterText = tabs
+ .Elements(W.tab)
+ .FirstOrDefault(t => (int)t.Attribute(W.pos) > testAmount);
+
+ if (tabAfterText == null)
+ {
+ // something has gone wrong, so put 1/2 inch in
+ if (currentElement.Attribute(PtOpenXml.TabWidth) == null)
+ currentElement.Add(
+ new XAttribute(PtOpenXml.TabWidth, 720m));
+ break;
+ }
+
+ var tabVal = (string)tabAfterText.Attribute(W.val);
+ if (tabVal == "right" || tabVal == "end")
+ {
+ var textAfterElements = contentToMeasure
+ .Skip(currentElementIdx + 1);
+
+ // take all the content until another tab, br, or cr
+ var textElementsToMeasure = textAfterElements
+ .TakeWhile(z =>
+ z.Name != W.tab &&
+ z.Name != W.br &&
+ z.Name != W.cr)
+ .ToList();
+
+ var textAfterTab = textElementsToMeasure
+ .Where(z => z.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate();
+
+ var dummyRun2 = new XElement(W.r,
+ fontNameAtt,
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, textAfterTab));
+
+ var widthOfTextAfterTab = CalcWidthOfRunInTwips(dummyRun2);
+ var delta2 = (int)tabAfterText.Attribute(W.pos) - widthOfTextAfterTab - twipCounter;
+ if (delta2 < 0)
+ delta2 = 0;
+ currentElement.Add(
+ new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
+ GetLeader(tabAfterText));
+ twipCounter = Math.Max((int)tabAfterText.Attribute(W.pos), twipCounter + widthOfTextAfterTab);
+
+ var lastElement = textElementsToMeasure.LastOrDefault();
+ if (lastElement == null)
+ break; // we're done
+
+ currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+
+ continue;
+ }
+ if (tabVal == "decimal")
+ {
+ var textAfterElements = contentToMeasure
+ .Skip(currentElementIdx + 1);
+
+ // take all the content until another tab, br, or cr
+ var textElementsToMeasure = textAfterElements
+ .TakeWhile(z =>
+ z.Name != W.tab &&
+ z.Name != W.br &&
+ z.Name != W.cr)
+ .ToList();
+
+ var textAfterTab = textElementsToMeasure
+ .Where(z => z.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate();
+
+ if (textAfterTab.Contains("."))
+ {
+ var mantissa = textAfterTab.Split('.')[0];
+
+ var dummyRun4 = new XElement(W.r,
+ fontNameAtt,
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, mantissa));
+
+ var widthOfMantissa = CalcWidthOfRunInTwips(dummyRun4);
+ var delta2 = (int)tabAfterText.Attribute(W.pos) - widthOfMantissa - twipCounter;
+ if (delta2 < 0)
+ delta2 = 0;
+ currentElement.Add(
+ new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
+ GetLeader(tabAfterText));
+
+ var decims = textAfterTab.Substring(textAfterTab.IndexOf('.'));
+ dummyRun4 = new XElement(W.r,
+ fontNameAtt,
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, decims));
+
+ var widthOfDecims = CalcWidthOfRunInTwips(dummyRun4);
+ twipCounter = Math.Max((int)tabAfterText.Attribute(W.pos) + widthOfDecims, twipCounter + widthOfMantissa + widthOfDecims);
+
+ var lastElement = textElementsToMeasure.LastOrDefault();
+ if (lastElement == null)
+ break; // we're done
+
+ currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+
+ continue;
+ }
+ else
+ {
+ var dummyRun2 = new XElement(W.r,
+ fontNameAtt,
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, textAfterTab));
+
+ var widthOfTextAfterTab = CalcWidthOfRunInTwips(dummyRun2);
+ var delta2 = (int)tabAfterText.Attribute(W.pos) - widthOfTextAfterTab - twipCounter;
+ if (delta2 < 0)
+ delta2 = 0;
+ currentElement.Add(
+ new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
+ GetLeader(tabAfterText));
+ twipCounter = Math.Max((int)tabAfterText.Attribute(W.pos), twipCounter + widthOfTextAfterTab);
+
+ var lastElement = textElementsToMeasure.LastOrDefault();
+ if (lastElement == null)
+ break; // we're done
+
+ currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+
+ continue;
+ }
+ }
+ if ((string)tabAfterText.Attribute(W.val) == "center")
+ {
+ var textAfterElements = contentToMeasure
+ .Skip(currentElementIdx + 1);
+
+ // take all the content until another tab, br, or cr
+ var textElementsToMeasure = textAfterElements
+ .TakeWhile(z =>
+ z.Name != W.tab &&
+ z.Name != W.br &&
+ z.Name != W.cr)
+ .ToList();
+
+ var textAfterTab = textElementsToMeasure
+ .Where(z => z.Name == W.t)
+ .Select(t => (string)t)
+ .StringConcatenate();
+
+ var dummyRun4 = new XElement(W.r,
+ fontNameAtt,
+ runContainingTabToReplace.Elements(W.rPr),
+ new XElement(W.t, textAfterTab));
+
+ var widthOfText = CalcWidthOfRunInTwips(dummyRun4);
+ var delta2 = (int)tabAfterText.Attribute(W.pos) - (widthOfText / 2) - twipCounter;
+ if (delta2 < 0)
+ delta2 = 0;
+ currentElement.Add(
+ new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta2 / 1440m)),
+ GetLeader(tabAfterText));
+ twipCounter = Math.Max((int)tabAfterText.Attribute(W.pos) + widthOfText / 2, twipCounter + widthOfText);
+
+ var lastElement = textElementsToMeasure.LastOrDefault();
+ if (lastElement == null)
+ break; // we're done
+
+ currentElementIdx = Array.IndexOf(contentToMeasure, lastElement) + 1;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+
+ continue;
+ }
+ if (tabVal == "left" || tabVal == "start" || tabVal == "num")
+ {
+ var delta = (int)tabAfterText.Attribute(W.pos) - twipCounter;
+ currentElement.Add(
+ new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal)delta / 1440m)),
+ GetLeader(tabAfterText));
+ twipCounter = (int)tabAfterText.Attribute(W.pos);
+
+ currentElementIdx++;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+
+ continue;
+ }
+ }
+
+ if (currentElement.Name == W.t)
+ {
+ // TODO: Revisit. This is a quick fix because it doesn't work on Azure.
+ // Given the changes we've made elsewhere, though, this is not required
+ // for the first tab at least. We could also enhance that other change
+ // to deal with all tabs.
+ //var runContainingTabToReplace = currentElement.Parent;
+ //var paragraphForRun = runContainingTabToReplace.Ancestors(W.p).First();
+ //var fontNameAtt = runContainingTabToReplace.Attribute(PtOpenXml.FontName) ??
+ // paragraphForRun.Attribute(PtOpenXml.FontName);
+ //var languageTypeAtt = runContainingTabToReplace.Attribute(PtOpenXml.LanguageType) ??
+ // paragraphForRun.Attribute(PtOpenXml.LanguageType);
+
+ //var dummyRun3 = new XElement(W.r, fontNameAtt, languageTypeAtt,
+ // runContainingTabToReplace.Elements(W.rPr),
+ // currentElement);
+ //var widthOfText = CalcWidthOfRunInTwips(dummyRun3);
+ const int widthOfText = 0;
+ currentElement.Add(new XAttribute(PtOpenXml.TabWidth,
+ string.Format(NumberFormatInfo.InvariantInfo, "{0:0.000}", (decimal) widthOfText/1440m)));
+ twipCounter += widthOfText;
+
+ currentElementIdx++;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+
+ continue;
+ }
+
+ currentElementIdx++;
+ if (currentElementIdx >= contentToMeasure.Length)
+ break; // we're done
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => CalculateSpanWidthTransform(n, defaultTabStop)));
+ }
+
+ private static XAttribute GetLeader(XElement tabAfterText)
+ {
+ var leader = (string)tabAfterText.Attribute(W.leader);
+ if (leader == null)
+ return null;
+ return new XAttribute(PtOpenXml.Leader, leader);
+ }
+
+ private static XElement AddDefaultTabsAfterLastTab(XElement tabs, int defaultTabStop)
+ {
+ var lastTabElement = tabs
+ .Elements(W.tab)
+ .Where(t => (string)t.Attribute(W.val) != "clear" && (string)t.Attribute(W.val) != "bar")
+ .OrderBy(t => (int)t.Attribute(W.pos))
+ .LastOrDefault();
+ if (lastTabElement != null)
+ {
+ if (defaultTabStop == 0)
+ defaultTabStop = 720;
+ var rangeStart = (int)lastTabElement.Attribute(W.pos) / defaultTabStop + 1;
+ var tempTabs = new XElement(W.tabs,
+ tabs.Elements().Where(t => (string)t.Attribute(W.val) != "clear" && (string)t.Attribute(W.val) != "bar"),
+ Enumerable.Range(rangeStart, 100)
+ .Select(r => new XElement(W.tab,
+ new XAttribute(W.val, "left"),
+ new XAttribute(W.pos, r * defaultTabStop))));
+ tempTabs = new XElement(W.tabs,
+ tempTabs.Elements().OrderBy(t => (int)t.Attribute(W.pos)));
+ return tempTabs;
+ }
+ else
+ {
+ tabs = new XElement(W.tabs,
+ Enumerable.Range(1, 100)
+ .Select(r => new XElement(W.tab,
+ new XAttribute(W.val, "left"),
+ new XAttribute(W.pos, r * defaultTabStop))));
+ }
+ return tabs;
+ }
+
+ private static readonly HashSet<string> UnknownFonts = new HashSet<string>();
+ private static HashSet<string> _knownFamilies;
+
+ private static HashSet<string> KnownFamilies
+ {
+ get
+ {
+ if (_knownFamilies == null)
+ {
+ _knownFamilies = new HashSet<string>();
+ var families = FontFamily.Families;
+ foreach (var fam in families)
+ _knownFamilies.Add(fam.Name);
+ }
+ return _knownFamilies;
+ }
+ }
+
+ private static int CalcWidthOfRunInTwips(XElement r)
+ {
+ var fontName = (string)r.Attribute(PtOpenXml.pt + "FontName") ??
+ (string)r.Ancestors(W.p).First().Attribute(PtOpenXml.pt + "FontName");
+ if (fontName == null)
+ throw new OpenXmlPowerToolsException("Internal Error, should have FontName attribute");
+ if (UnknownFonts.Contains(fontName))
+ return 0;
+
+ var rPr = r.Element(W.rPr);
+ if (rPr == null)
+ throw new OpenXmlPowerToolsException("Internal Error, should have run properties");
+
+ var sz = GetFontSize(r) ?? 22m;
+
+ // unknown font families will throw ArgumentException, in which case just return 0
+ if (!KnownFamilies.Contains(fontName))
+ return 0;
+
+ // in theory, all unknown fonts are found by the above test, but if not...
+ FontFamily ff;
+ try
+ {
+ ff = new FontFamily(fontName);
+ }
+ catch (ArgumentException)
+ {
+ UnknownFonts.Add(fontName);
+
+ return 0;
+ }
+
+ var fs = FontStyle.Regular;
+ if (GetBoolProp(rPr, W.b) || GetBoolProp(rPr, W.bCs))
+ fs |= FontStyle.Bold;
+ if (GetBoolProp(rPr, W.i) || GetBoolProp(rPr, W.iCs))
+ fs |= FontStyle.Italic;
+
+ // Appended blank as a quick fix to accommodate that will get
+ // appended to some layout-critical runs such as list item numbers.
+ // In some cases, this might not be required or even wrong, so this
+ // must be revisited.
+ // TODO: Revisit.
+ var runText = r.DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.t)
+ .Select(t => (string) t)
+ .StringConcatenate() + " ";
+
+ var tabLength = r.DescendantsTrimmed(W.txbxContent)
+ .Where(e => e.Name == W.tab)
+ .Select(t => (decimal)t.Attribute(PtOpenXml.TabWidth))
+ .Sum();
+
+ if (runText.Length == 0 && tabLength == 0)
+ return 0;
+
+ int multiplier = 1;
+ if (runText.Length <= 2)
+ multiplier = 100;
+ else if (runText.Length <= 4)
+ multiplier = 50;
+ else if (runText.Length <= 8)
+ multiplier = 25;
+ else if (runText.Length <= 16)
+ multiplier = 12;
+ else if (runText.Length <= 32)
+ multiplier = 6;
+ if (multiplier != 1)
+ {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < multiplier; i++)
+ sb.Append(runText);
+ runText = sb.ToString();
+ }
+
+ try
+ {
+ using (Font f = new Font(ff, (float) sz/2f, fs))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ const decimal dpi = 96m;
+ var twip = (int) (((sf.Width/dpi)*1440m)/multiplier + tabLength*1440m);
+ return twip;
+ }
+ }
+ catch (ArgumentException)
+ {
+ try
+ {
+ const FontStyle fs2 = FontStyle.Regular;
+ using (Font f = new Font(ff, (float) sz/2f, fs2))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ const decimal dpi = 96m;
+ var twip = (int) (((sf.Width/dpi)*1440m)/multiplier + tabLength*1440m);
+ return twip;
+ }
+ }
+ catch (ArgumentException)
+ {
+ const FontStyle fs2 = FontStyle.Bold;
+ try
+ {
+ using (var f = new Font(ff, (float) sz/2f, fs2))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ const decimal dpi = 96m;
+ var twip = (int) (((sf.Width/dpi)*1440m)/multiplier + tabLength*1440m);
+ return twip;
+ }
+ }
+ catch (ArgumentException)
+ {
+ // if both regular and bold fail, then get metrics for Times New Roman
+ // use the original FontStyle (in fs)
+ var ff2 = new FontFamily("Times New Roman");
+ using (var f = new Font(ff2, (float) sz/2f, fs))
+ {
+ const TextFormatFlags tff = TextFormatFlags.NoPadding;
+ var proposedSize = new Size(int.MaxValue, int.MaxValue);
+ var sf = TextRenderer.MeasureText(runText, f, proposedSize, tff);
+ // sf returns size in pixels
+ const decimal dpi = 96m;
+ var twip = (int) (((sf.Width/dpi)*1440m)/multiplier + tabLength*1440m);
+ return twip;
+ }
+ }
+ }
+ }
+ catch (OverflowException)
+ {
+ // This happened on Azure but interestingly enough not while testing locally.
+ return 0;
+ }
+ }
+
+ private static void InsertAppropriateNonbreakingSpaces(WordprocessingDocument wordDoc)
+ {
+ foreach (var part in wordDoc.ContentParts())
+ {
+ var pxd = part.GetXDocument();
+ var root = pxd.Root;
+ if (root == null) return;
+
+ var newRoot = (XElement)InsertAppropriateNonbreakingSpacesTransform(root);
+ root.ReplaceWith(newRoot);
+ part.PutXDocument();
+ }
+ }
+
+ // Non-breaking spaces are not required if we use appropriate CSS, i.e., "white-space: pre-wrap;".
+ // We only need to make sure that empty w:p elements are translated into non-empty h:p elements,
+ // because empty h:p elements would be ignored by browsers.
+ // Further, in addition to not being required, non-breaking spaces would change the layout behavior
+ // of spans having consecutive spaces. Therefore, avoiding non-breaking spaces has the additional
+ // benefit of leading to a more faithful representation of the Word document in HTML.
+ private static object InsertAppropriateNonbreakingSpacesTransform(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ // child content of run to look for
+ // W.br
+ // W.cr
+ // W.dayLong
+ // W.dayShort
+ // W.drawing
+ // W.monthLong
+ // W.monthShort
+ // W.noBreakHyphen
+ // W.object
+ // W.pgNum
+ // W.pTab
+ // W.separator
+ // W.softHyphen
+ // W.sym
+ // W.t
+ // W.tab
+ // W.yearLong
+ // W.yearShort
+ if (element.Name == W.p)
+ {
+ // Translate empty paragraphs to paragraphs having one run with
+ // a normal space. A non-breaking space, i.e., \x00A0, is not
+ // required if we use appropriate CSS.
+ bool hasContent = element
+ .Elements()
+ .Where(e => e.Name != W.pPr)
+ .DescendantsAndSelf()
+ .Any(e =>
+ e.Name == W.dayLong ||
+ e.Name == W.dayShort ||
+ e.Name == W.drawing ||
+ e.Name == W.monthLong ||
+ e.Name == W.monthShort ||
+ e.Name == W.noBreakHyphen ||
+ e.Name == W._object ||
+ e.Name == W.pgNum ||
+ e.Name == W.ptab ||
+ e.Name == W.separator ||
+ e.Name == W.softHyphen ||
+ e.Name == W.sym ||
+ e.Name == W.t ||
+ e.Name == W.tab ||
+ e.Name == W.yearLong ||
+ e.Name == W.yearShort
+ );
+ if (hasContent == false)
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => InsertAppropriateNonbreakingSpacesTransform(n)),
+ new XElement(W.r,
+ element.Elements(W.pPr).Elements(W.rPr),
+ new XElement(W.t, " ")));
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => InsertAppropriateNonbreakingSpacesTransform(n)));
+ }
+ return node;
+ }
+
+ private class SectionAnnotation
+ {
+ public XElement SectionElement;
+ }
+
+ private static void AnnotateForSections(WordprocessingDocument wordDoc)
+ {
+ var xd = wordDoc.MainDocumentPart.GetXDocument();
+
+ var document = xd.Root;
+ if (document == null) return;
+
+ var body = document.Element(W.body);
+ if (body == null) return;
+
+ // move last sectPr into last paragraph
+ var lastSectPr = body.Elements(W.sectPr).LastOrDefault();
+ if (lastSectPr != null)
+ {
+ // if the last thing in the document is a table, Word will always insert a paragraph following that.
+ var lastPara = body
+ .DescendantsTrimmed(W.txbxContent)
+ .LastOrDefault(p => p.Name == W.p);
+
+ if (lastPara != null)
+ {
+ var lastParaProps = lastPara.Element(W.pPr);
+ if (lastParaProps != null)
+ lastParaProps.Add(lastSectPr);
+ else
+ lastPara.Add(new XElement(W.pPr, lastSectPr));
+
+ lastSectPr.Remove();
+ }
+ }
+
+ var reverseDescendants = xd.Descendants().Reverse().ToList();
+ var currentSection = InitializeSectionAnnotation(reverseDescendants);
+
+ foreach (var d in reverseDescendants)
+ {
+ if (d.Name == W.sectPr)
+ {
+ if (d.Attribute(XNamespace.Xmlns + "w") == null)
+ d.Add(new XAttribute(XNamespace.Xmlns + "w", W.w));
+
+ currentSection = new SectionAnnotation()
+ {
+ SectionElement = d
+ };
+ }
+ else
+ d.AddAnnotation(currentSection);
+ }
+ }
+
+ private static SectionAnnotation InitializeSectionAnnotation(IEnumerable<XElement> reverseDescendants)
+ {
+ var currentSection = new SectionAnnotation()
+ {
+ SectionElement = reverseDescendants.FirstOrDefault(e => e.Name == W.sectPr)
+ };
+ if (currentSection.SectionElement != null &&
+ currentSection.SectionElement.Attribute(XNamespace.Xmlns + "w") == null)
+ currentSection.SectionElement.Add(new XAttribute(XNamespace.Xmlns + "w", W.w));
+
+ // todo what should the default section props be?
+ if (currentSection.SectionElement == null)
+ currentSection = new SectionAnnotation()
+ {
+ SectionElement = new XElement(W.sectPr,
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XElement(W.pgSz,
+ new XAttribute(W._w, 12240),
+ new XAttribute(W.h, 15840)),
+ new XElement(W.pgMar,
+ new XAttribute(W.top, 1440),
+ new XAttribute(W.right, 1440),
+ new XAttribute(W.bottom, 1440),
+ new XAttribute(W.left, 1440),
+ new XAttribute(W.header, 720),
+ new XAttribute(W.footer, 720),
+ new XAttribute(W.gutter, 0)),
+ new XElement(W.cols,
+ new XAttribute(W.space, 720)),
+ new XElement(W.docGrid,
+ new XAttribute(W.linePitch, 360)))
+ };
+
+ return currentSection;
+ }
+
+ private static object CreateBorderDivs(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings, IEnumerable<XElement> elements)
+ {
+ return elements.GroupAdjacent(e =>
+ {
+ var pBdr = e.Elements(W.pPr).Elements(W.pBdr).FirstOrDefault();
+ if (pBdr != null)
+ {
+ var indStr = string.Empty;
+ var ind = e.Elements(W.pPr).Elements(W.ind).FirstOrDefault();
+ if (ind != null)
+ indStr = ind.ToString(SaveOptions.DisableFormatting);
+ return pBdr.ToString(SaveOptions.DisableFormatting) + indStr;
+ }
+ return e.Name == W.tbl ? "table" : string.Empty;
+ })
+ .Select(g =>
+ {
+ if (g.Key == string.Empty)
+ {
+ return (object) GroupAndVerticallySpaceNumberedParagraphs(wordDoc, settings, g, 0m);
+ }
+ if (g.Key == "table")
+ {
+ return g.Select(gc => ConvertToHtmlTransform(wordDoc, settings, gc, false, 0));
+ }
+ var pPr = g.First().Elements(W.pPr).First();
+ var pBdr = pPr.Element(W.pBdr);
+ var style = new Dictionary<string, string>();
+ GenerateBorderStyle(pBdr, W.top, style, BorderType.Paragraph);
+ GenerateBorderStyle(pBdr, W.right, style, BorderType.Paragraph);
+ GenerateBorderStyle(pBdr, W.bottom, style, BorderType.Paragraph);
+ GenerateBorderStyle(pBdr, W.left, style, BorderType.Paragraph);
+
+ var currentMarginLeft = 0m;
+ var ind = pPr.Element(W.ind);
+ if (ind != null)
+ {
+ var leftInInches = (decimal?) ind.Attribute(W.left)/1440m ?? 0;
+ var hangingInInches = -(decimal?) ind.Attribute(W.hanging)/1440m ?? 0;
+ currentMarginLeft = leftInInches + hangingInInches;
+
+ style.AddIfMissing("margin-left",
+ currentMarginLeft > 0m
+ ? string.Format(NumberFormatInfo.InvariantInfo, "{0:0.00}in", currentMarginLeft)
+ : "0");
+ }
+
+ var div = new XElement(Xhtml.div,
+ GroupAndVerticallySpaceNumberedParagraphs(wordDoc, settings, g, currentMarginLeft));
+ div.AddAnnotation(style);
+ return div;
+ })
+ .ToList();
+ }
+
+ private static IEnumerable<object> GroupAndVerticallySpaceNumberedParagraphs(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
+ IEnumerable<XElement> elements, decimal currentMarginLeft)
+ {
+ var grouped = elements
+ .GroupAdjacent(e =>
+ {
+ var abstractNumId = (string)e.Attribute(PtOpenXml.pt + "AbstractNumId");
+ if (abstractNumId != null)
+ return "num:" + abstractNumId;
+ var contextualSpacing = e.Elements(W.pPr).Elements(W.contextualSpacing).FirstOrDefault();
+ if (contextualSpacing != null)
+ {
+ var styleName = (string)e.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
+ if (styleName == null)
+ return "";
+ return "sty:" + styleName;
+ }
+ return "";
+ })
+ .ToList();
+ var newContent = grouped
+ .Select(g =>
+ {
+ if (g.Key == "")
+ return g.Select(e => ConvertToHtmlTransform(wordDoc, settings, e, false, currentMarginLeft));
+ var last = g.Count() - 1;
+ return g.Select((e, i) => ConvertToHtmlTransform(wordDoc, settings, e, i != last, currentMarginLeft));
+ });
+ return (IEnumerable<object>)newContent;
+ }
+
+ private class BorderMappingInfo
+ {
+ public string CssName;
+ public decimal CssSize;
+ }
+
+ private static readonly Dictionary<string, BorderMappingInfo> BorderStyleMap = new Dictionary<string, BorderMappingInfo>()
+ {
+ { "single", new BorderMappingInfo() { CssName = "solid", CssSize = 1.0m }},
+ { "dotted", new BorderMappingInfo() { CssName = "dotted", CssSize = 1.0m }},
+ { "dashSmallGap", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
+ { "dashed", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
+ { "dotDash", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
+ { "dotDotDash", new BorderMappingInfo() { CssName = "dashed", CssSize = 1.0m }},
+ { "double", new BorderMappingInfo() { CssName = "double", CssSize = 2.5m }},
+ { "triple", new BorderMappingInfo() { CssName = "double", CssSize = 2.5m }},
+ { "thinThickSmallGap", new BorderMappingInfo() { CssName = "double", CssSize = 4.5m }},
+ { "thickThinSmallGap", new BorderMappingInfo() { CssName = "double", CssSize = 4.5m }},
+ { "thinThickThinSmallGap", new BorderMappingInfo() { CssName = "double", CssSize = 6.0m }},
+ { "thickThinMediumGap", new BorderMappingInfo() { CssName = "double", CssSize = 6.0m }},
+ { "thinThickMediumGap", new BorderMappingInfo() { CssName = "double", CssSize = 6.0m }},
+ { "thinThickThinMediumGap", new BorderMappingInfo() { CssName = "double", CssSize = 9.0m }},
+ { "thinThickLargeGap", new BorderMappingInfo() { CssName = "double", CssSize = 5.25m }},
+ { "thickThinLargeGap", new BorderMappingInfo() { CssName = "double", CssSize = 5.25m }},
+ { "thinThickThinLargeGap", new BorderMappingInfo() { CssName = "double", CssSize = 9.0m }},
+ { "wave", new BorderMappingInfo() { CssName = "solid", CssSize = 3.0m }},
+ { "doubleWave", new BorderMappingInfo() { CssName = "double", CssSize = 5.25m }},
+ { "dashDotStroked", new BorderMappingInfo() { CssName = "solid", CssSize = 3.0m }},
+ { "threeDEmboss", new BorderMappingInfo() { CssName = "ridge", CssSize = 6.0m }},
+ { "threeDEngrave", new BorderMappingInfo() { CssName = "groove", CssSize = 6.0m }},
+ { "outset", new BorderMappingInfo() { CssName = "outset", CssSize = 4.5m }},
+ { "inset", new BorderMappingInfo() { CssName = "inset", CssSize = 4.5m }},
+ };
+
+ private static void GenerateBorderStyle(XElement pBdr, XName sideXName, Dictionary<string, string> style, BorderType borderType)
+ {
+ string whichSide;
+ if (sideXName == W.top)
+ whichSide = "top";
+ else if (sideXName == W.right)
+ whichSide = "right";
+ else if (sideXName == W.bottom)
+ whichSide = "bottom";
+ else
+ whichSide = "left";
+ if (pBdr == null)
+ {
+ style.Add("border-" + whichSide, "none");
+ if (borderType == BorderType.Cell &&
+ (whichSide == "left" || whichSide == "right"))
+ style.Add("padding-" + whichSide, "5.4pt");
+ return;
+ }
+
+ var side = pBdr.Element(sideXName);
+ if (side == null)
+ {
+ style.Add("border-" + whichSide, "none");
+ if (borderType == BorderType.Cell &&
+ (whichSide == "left" || whichSide == "right"))
+ style.Add("padding-" + whichSide, "5.4pt");
+ return;
+ }
+ var type = (string)side.Attribute(W.val);
+ if (type == "nil" || type == "none")
+ {
+ style.Add("border-" + whichSide + "-style", "none");
+
+ var space = (decimal?)side.Attribute(W.space) ?? 0;
+ if (borderType == BorderType.Cell &&
+ (whichSide == "left" || whichSide == "right"))
+ if (space < 5.4m)
+ space = 5.4m;
+ style.Add("padding-" + whichSide,
+ space == 0 ? "0" : string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", space));
+
+ }
+ else
+ {
+ var sz = (int)side.Attribute(W.sz);
+ var space = (decimal?)side.Attribute(W.space) ?? 0;
+ var color = (string)side.Attribute(W.color);
+ if (color == null || color == "auto")
+ color = "windowtext";
+ else
+ color = ConvertColor(color);
+
+ decimal borderWidthInPoints = Math.Max(1m, Math.Min(96m, Math.Max(2m, sz)) / 8m);
+
+ var borderStyle = "solid";
+ if (BorderStyleMap.ContainsKey(type))
+ {
+ var borderInfo = BorderStyleMap[type];
+ borderStyle = borderInfo.CssName;
+ if (type == "double")
+ {
+ if (sz <= 8)
+ borderWidthInPoints = 2.5m;
+ else if (sz <= 18)
+ borderWidthInPoints = 6.75m;
+ else
+ borderWidthInPoints = sz / 3m;
+ }
+ else if (type == "triple")
+ {
+ if (sz <= 8)
+ borderWidthInPoints = 8m;
+ else if (sz <= 18)
+ borderWidthInPoints = 11.25m;
+ else
+ borderWidthInPoints = 11.25m;
+ }
+ else if (type.ToLower().Contains("dash"))
+ {
+ if (sz <= 4)
+ borderWidthInPoints = 1m;
+ else if (sz <= 12)
+ borderWidthInPoints = 1.5m;
+ else
+ borderWidthInPoints = 2m;
+ }
+ else if (type != "single")
+ borderWidthInPoints = borderInfo.CssSize;
+ }
+ if (type == "outset" || type == "inset")
+ color = "";
+ var borderWidth = string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", borderWidthInPoints);
+
+ style.Add("border-" + whichSide, borderStyle + " " + color + " " + borderWidth);
+ if (borderType == BorderType.Cell &&
+ (whichSide == "left" || whichSide == "right"))
+ if (space < 5.4m)
+ space = 5.4m;
+
+ style.Add("padding-" + whichSide,
+ space == 0 ? "0" : string.Format(NumberFormatInfo.InvariantInfo, "{0:0.0}pt", space));
+ }
+ }
+
+ private static readonly Dictionary<string, Func<string, string, string>> ShadeMapper = new Dictionary<string,Func<string, string, string>>()
+ {
+ { "auto", (c, f) => c },
+ { "clear", (c, f) => f },
+ { "nil", (c, f) => f },
+ { "solid", (c, f) => c },
+ { "diagCross", (c, f) => ConvertColorFillPct(c, f, .75) },
+ { "diagStripe", (c, f) => ConvertColorFillPct(c, f, .75) },
+ { "horzCross", (c, f) => ConvertColorFillPct(c, f, .5) },
+ { "horzStripe", (c, f) => ConvertColorFillPct(c, f, .5) },
+ { "pct10", (c, f) => ConvertColorFillPct(c, f, .1) },
+ { "pct12", (c, f) => ConvertColorFillPct(c, f, .125) },
+ { "pct15", (c, f) => ConvertColorFillPct(c, f, .15) },
+ { "pct20", (c, f) => ConvertColorFillPct(c, f, .2) },
+ { "pct25", (c, f) => ConvertColorFillPct(c, f, .25) },
+ { "pct30", (c, f) => ConvertColorFillPct(c, f, .3) },
+ { "pct35", (c, f) => ConvertColorFillPct(c, f, .35) },
+ { "pct37", (c, f) => ConvertColorFillPct(c, f, .375) },
+ { "pct40", (c, f) => ConvertColorFillPct(c, f, .4) },
+ { "pct45", (c, f) => ConvertColorFillPct(c, f, .45) },
+ { "pct50", (c, f) => ConvertColorFillPct(c, f, .50) },
+ { "pct55", (c, f) => ConvertColorFillPct(c, f, .55) },
+ { "pct60", (c, f) => ConvertColorFillPct(c, f, .60) },
+ { "pct62", (c, f) => ConvertColorFillPct(c, f, .625) },
+ { "pct65", (c, f) => ConvertColorFillPct(c, f, .65) },
+ { "pct70", (c, f) => ConvertColorFillPct(c, f, .7) },
+ { "pct75", (c, f) => ConvertColorFillPct(c, f, .75) },
+ { "pct80", (c, f) => ConvertColorFillPct(c, f, .8) },
+ { "pct85", (c, f) => ConvertColorFillPct(c, f, .85) },
+ { "pct87", (c, f) => ConvertColorFillPct(c, f, .875) },
+ { "pct90", (c, f) => ConvertColorFillPct(c, f, .9) },
+ { "pct95", (c, f) => ConvertColorFillPct(c, f, .95) },
+ { "reverseDiagStripe", (c, f) => ConvertColorFillPct(c, f, .5) },
+ { "thinDiagCross", (c, f) => ConvertColorFillPct(c, f, .5) },
+ { "thinDiagStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
+ { "thinHorzCross", (c, f) => ConvertColorFillPct(c, f, .3) },
+ { "thinHorzStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
+ { "thinReverseDiagStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
+ { "thinVertStripe", (c, f) => ConvertColorFillPct(c, f, .25) },
+ };
+
+ private static readonly Dictionary<string, string> ShadeCache = new Dictionary<string, string>();
+
+ // fill is the background, color is the foreground
+ private static string ConvertColorFillPct(string color, string fill, double pct)
+ {
+ if (color == "auto")
+ color = "000000";
+ if (fill == "auto")
+ fill = "ffffff";
+ var key = color + fill + pct.ToString(CultureInfo.InvariantCulture);
+ if (ShadeCache.ContainsKey(key))
+ return ShadeCache[key];
+ var fillRed = Convert.ToInt32(fill.Substring(0, 2), 16);
+ var fillGreen = Convert.ToInt32(fill.Substring(2, 2), 16);
+ var fillBlue = Convert.ToInt32(fill.Substring(4, 2), 16);
+ var colorRed = Convert.ToInt32(color.Substring(0, 2), 16);
+ var colorGreen = Convert.ToInt32(color.Substring(2, 2), 16);
+ var colorBlue = Convert.ToInt32(color.Substring(4, 2), 16);
+ var finalRed = (int)(fillRed - (fillRed - colorRed) * pct);
+ var finalGreen = (int)(fillGreen - (fillGreen - colorGreen) * pct);
+ var finalBlue = (int)(fillBlue - (fillBlue - colorBlue) * pct);
+ var returnValue = string.Format("{0:x2}{1:x2}{2:x2}", finalRed, finalGreen, finalBlue);
+ ShadeCache.Add(key, returnValue);
+ return returnValue;
+ }
+
+ private static void CreateStyleFromShd(Dictionary<string, string> style, XElement shd)
+ {
+ if (shd == null)
+ return;
+ var shadeType = (string)shd.Attribute(W.val);
+ var color = (string)shd.Attribute(W.color);
+ var fill = (string)shd.Attribute(W.fill);
+ if (ShadeMapper.ContainsKey(shadeType))
+ {
+ color = ShadeMapper[shadeType](color, fill);
+ }
+ if (color != null)
+ {
+ var cvtColor = ConvertColor(color);
+ if (!string.IsNullOrEmpty(cvtColor))
+ style.AddIfMissing("background", cvtColor);
+ }
+ }
+
+ private static readonly Dictionary<string, string> NamedColors = new Dictionary<string, string>()
+ {
+ {"black", "black"},
+ {"blue", "blue" },
+ {"cyan", "aqua" },
+ {"green", "green" },
+ {"magenta", "fuchsia" },
+ {"red", "red" },
+ {"yellow", "yellow" },
+ {"white", "white" },
+ {"darkBlue", "#00008B" },
+ {"darkCyan", "#008B8B" },
+ {"darkGreen", "#006400" },
+ {"darkMagenta", "#800080" },
+ {"darkRed", "#8B0000" },
+ {"darkYellow", "#808000" },
+ {"darkGray", "#A9A9A9" },
+ {"lightGray", "#D3D3D3" },
+ {"none", "" },
+ };
+
+ private static void CreateColorProperty(string propertyName, string color, Dictionary<string, string> style)
+ {
+ if (color == null)
+ return;
+
+ // "auto" color is black for "color" and white for "background" property.
+ if (color == "auto")
+ color = propertyName == "color" ? "black" : "white";
+
+ if (NamedColors.ContainsKey(color))
+ {
+ var lc = NamedColors[color];
+ if (lc == "")
+ return;
+ style.AddIfMissing(propertyName, lc);
+ return;
+ }
+ style.AddIfMissing(propertyName, "#" + color);
+ }
+
+ private static string ConvertColor(string color)
+ {
+ // "auto" color is black for "color" and white for "background" property.
+ // As this method is only called for "background" colors, "auto" is translated
+ // to "white" and never "black".
+ if (color == "auto")
+ color = "white";
+
+ if (NamedColors.ContainsKey(color))
+ {
+ var lc = NamedColors[color];
+ if (lc == "")
+ return "black";
+ return lc;
+ }
+ return "#" + color;
+ }
+
+ private static readonly Dictionary<string, string> FontFallback = new Dictionary<string, string>()
+ {
+ { "Arial", @"'{0}', 'sans-serif'" },
+ { "Arial Narrow", @"'{0}', 'sans-serif'" },
+ { "Arial Rounded MT Bold", @"'{0}', 'sans-serif'" },
+ { "Arial Unicode MS", @"'{0}', 'sans-serif'" },
+ { "Baskerville Old Face", @"'{0}', 'serif'" },
+ { "Berlin Sans FB", @"'{0}', 'sans-serif'" },
+ { "Berlin Sans FB Demi", @"'{0}', 'sans-serif'" },
+ { "Calibri Light", @"'{0}', 'sans-serif'" },
+ { "Gill Sans MT", @"'{0}', 'sans-serif'" },
+ { "Gill Sans MT Condensed", @"'{0}', 'sans-serif'" },
+ { "Lucida Sans", @"'{0}', 'sans-serif'" },
+ { "Lucida Sans Unicode", @"'{0}', 'sans-serif'" },
+ { "Segoe UI", @"'{0}', 'sans-serif'" },
+ { "Segoe UI Light", @"'{0}', 'sans-serif'" },
+ { "Segoe UI Semibold", @"'{0}', 'sans-serif'" },
+ { "Tahoma", @"'{0}', 'sans-serif'" },
+ { "Trebuchet MS", @"'{0}', 'sans-serif'" },
+ { "Verdana", @"'{0}', 'sans-serif'" },
+ { "Book Antiqua", @"'{0}', 'serif'" },
+ { "Bookman Old Style", @"'{0}', 'serif'" },
+ { "Californian FB", @"'{0}', 'serif'" },
+ { "Cambria", @"'{0}', 'serif'" },
+ { "Constantia", @"'{0}', 'serif'" },
+ { "Garamond", @"'{0}', 'serif'" },
+ { "Lucida Bright", @"'{0}', 'serif'" },
+ { "Lucida Fax", @"'{0}', 'serif'" },
+ { "Palatino Linotype", @"'{0}', 'serif'" },
+ { "Times New Roman", @"'{0}', 'serif'" },
+ { "Wide Latin", @"'{0}', 'serif'" },
+ { "Courier New", @"'{0}'" },
+ { "Lucida Console", @"'{0}'" },
+ };
+
+ private static void CreateFontCssProperty(string font, Dictionary<string, string> style)
+ {
+ if (FontFallback.ContainsKey(font))
+ {
+ style.AddIfMissing("font-family", string.Format(FontFallback[font], font));
+ return;
+ }
+ style.AddIfMissing("font-family", font);
+ }
+
+ private static bool GetBoolProp(XElement runProps, XName xName)
+ {
+ var p = runProps.Element(xName);
+ if (p == null)
+ return false;
+ var v = p.Attribute(W.val);
+ if (v == null)
+ return true;
+ var s = v.Value.ToLower();
+ if (s == "0" || s == "false")
+ return false;
+ if (s == "1" || s == "true")
+ return true;
+ return false;
+ }
+
+ private static object ConvertContentThatCanContainFields(WordprocessingDocument wordDoc, WmlToHtmlConverterSettings settings,
+ IEnumerable<XElement> elements)
+ {
+ var grouped = elements
+ .GroupAdjacent(e =>
+ {
+ var stack = e.Annotation<Stack<FieldRetriever.FieldElementTypeInfo>>();
+ return stack == null || !stack.Any() ? (int?)null : stack.Select(st => st.Id).Min();
+ })
+ .ToList();
+
+ var txformed = grouped
+ .Select(g =>
+ {
+ var key = g.Key;
+ if (key == null)
+ return (object)g.Select(n => ConvertToHtmlTransform(wordDoc, settings, n, false, 0m));
+
+ var instrText = FieldRetriever.InstrText(g.First().Ancestors().Last(), (int)key)
+ .TrimStart('{').TrimEnd('}');
+
+ var parsed = FieldRetriever.ParseField(instrText);
+ if (parsed.FieldType != "HYPERLINK")
+ return g.Select(n => ConvertToHtmlTransform(wordDoc, settings, n, false, 0m));
+
+ var content = g.DescendantsAndSelf(W.r).Select(run => ConvertRun(wordDoc, settings, run));
+ var a = parsed.Arguments.Length > 0
+ ? new XElement(Xhtml.a, new XAttribute("href", parsed.Arguments[0]), content)
+ : new XElement(Xhtml.a, content);
+ var a2 = a as XElement;
+ if (!a2.Nodes().Any())
+ {
+ a2.Add(new XText(""));
+ return a2;
+ }
+ return a;
+ })
+ .ToList();
+
+ return txformed;
+ }
+
+ #region Image Processing
+
+ // Don't process wmf files (with contentType == "image/x-wmf") because GDI consumes huge amounts
+ // of memory when dealing with wmf perhaps because it loads a DLL to do the rendering?
+ // It actually works, but is not recommended.
+ private static readonly List<string> ImageContentTypes = new List<string>
+ {
+ "image/png", "image/gif", "image/tiff", "image/jpeg"
+ };
+
+
+ public static XElement ProcessImage(WordprocessingDocument wordDoc,
+ XElement element, Func<ImageInfo, XElement> imageHandler)
+ {
+ if (imageHandler == null)
+ {
+ return null;
+ }
+ if (element.Name == W.drawing)
+ {
+ return ProcessDrawing(wordDoc, element, imageHandler);
+ }
+ if (element.Name == W.pict || element.Name == W._object)
+ {
+ return ProcessPictureOrObject(wordDoc, element, imageHandler);
+ }
+ return null;
+ }
+
+ private static XElement ProcessDrawing(WordprocessingDocument wordDoc,
+ XElement element, Func<ImageInfo, XElement> imageHandler)
+ {
+ var containerElement = element.Elements()
+ .FirstOrDefault(e => e.Name == WP.inline || e.Name == WP.anchor);
+ if (containerElement == null) return null;
+
+ string hyperlinkUri = null;
+ var hyperlinkElement = element
+ .Elements(WP.inline)
+ .Elements(WP.docPr)
+ .Elements(A.hlinkClick)
+ .FirstOrDefault();
+ if (hyperlinkElement != null)
+ {
+ var rId = (string)hyperlinkElement.Attribute(R.id);
+ if (rId != null)
+ {
+ var hyperlinkRel = wordDoc.MainDocumentPart.HyperlinkRelationships.FirstOrDefault(hlr => hlr.Id == rId);
+ if (hyperlinkRel != null)
+ {
+ hyperlinkUri = hyperlinkRel.Uri.ToString();
+ }
+ }
+ }
+
+ var extentCx = (int?)containerElement.Elements(WP.extent)
+ .Attributes(NoNamespace.cx).FirstOrDefault();
+ var extentCy = (int?)containerElement.Elements(WP.extent)
+ .Attributes(NoNamespace.cy).FirstOrDefault();
+ var altText = (string)containerElement.Elements(WP.docPr).Attributes(NoNamespace.descr).FirstOrDefault() ??
+ ((string)containerElement.Elements(WP.docPr).Attributes(NoNamespace.name).FirstOrDefault() ?? "");
+
+ var blipFill = containerElement.Elements(A.graphic)
+ .Elements(A.graphicData)
+ .Elements(Pic._pic).Elements(Pic.blipFill).FirstOrDefault();
+ if (blipFill == null) return null;
+
+ var imageRid = (string)blipFill.Elements(A.blip).Attributes(R.embed).FirstOrDefault();
+ if (imageRid == null) return null;
+
+ var pp3 = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp => pp.RelationshipId == imageRid);
+ if (pp3 == null) return null;
+
+ var imagePart = (ImagePart)pp3.OpenXmlPart;
+ if (imagePart == null) return null;
+
+ // If the image markup points to a NULL image, then following will throw an ArgumentOutOfRangeException
+ try
+ {
+ imagePart = (ImagePart)wordDoc.MainDocumentPart.GetPartById(imageRid);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ return null;
+ }
+
+ var contentType = imagePart.ContentType;
+ if (!ImageContentTypes.Contains(contentType))
+ return null;
+
+ using (var partStream = imagePart.GetStream())
+ using (var bitmap = new Bitmap(partStream))
+ {
+ if (extentCx != null && extentCy != null)
+ {
+ var imageInfo = new ImageInfo()
+ {
+ Bitmap = bitmap,
+ ImgStyleAttribute = new XAttribute("style",
+ string.Format(NumberFormatInfo.InvariantInfo,
+ "width: {0}in; height: {1}in",
+ (float)extentCx / (float)ImageInfo.EmusPerInch,
+ (float)extentCy / (float)ImageInfo.EmusPerInch)),
+ ContentType = contentType,
+ DrawingElement = element,
+ AltText = altText,
+ };
+ var imgElement2 = imageHandler(imageInfo);
+ if (hyperlinkUri != null)
+ {
+ return new XElement(XhtmlNoNamespace.a,
+ new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
+ imgElement2);
+ }
+ return imgElement2;
+ }
+
+ var imageInfo2 = new ImageInfo()
+ {
+ Bitmap = bitmap,
+ ContentType = contentType,
+ DrawingElement = element,
+ AltText = altText,
+ };
+ var imgElement = imageHandler(imageInfo2);
+ if (hyperlinkUri != null)
+ {
+ return new XElement(XhtmlNoNamespace.a,
+ new XAttribute(XhtmlNoNamespace.href, hyperlinkUri),
+ imgElement);
+ }
+ return imgElement;
+ }
+ }
+
+ private static XElement ProcessPictureOrObject(WordprocessingDocument wordDoc,
+ XElement element, Func<ImageInfo, XElement> imageHandler)
+ {
+ var imageRid = (string)element.Elements(VML.shape).Elements(VML.imagedata).Attributes(R.id).FirstOrDefault();
+ if (imageRid == null) return null;
+
+ try
+ {
+ var pp = wordDoc.MainDocumentPart.Parts.FirstOrDefault(pp2 => pp2.RelationshipId == imageRid);
+ if (pp == null) return null;
+
+ var imagePart = (ImagePart)pp.OpenXmlPart;
+ if (imagePart == null) return null;
+
+ var contentType = imagePart.ContentType;
+ if (!ImageContentTypes.Contains(contentType))
+ return null;
+
+ using (var partStream = imagePart.GetStream())
+ {
+ try
+ {
+ using (var bitmap = new Bitmap(partStream))
+ {
+ var imageInfo = new ImageInfo()
+ {
+ Bitmap = bitmap,
+ ContentType = contentType,
+ DrawingElement = element
+ };
+
+ var style = (string)element.Elements(VML.shape).Attributes("style").FirstOrDefault();
+ if (style == null) return imageHandler(imageInfo);
+
+ var tokens = style.Split(';');
+ var widthInPoints = WidthInPoints(tokens);
+ var heightInPoints = HeightInPoints(tokens);
+ if (widthInPoints != null && heightInPoints != null)
+ {
+ imageInfo.ImgStyleAttribute = new XAttribute("style",
+ string.Format(NumberFormatInfo.InvariantInfo,
+ "width: {0}pt; height: {1}pt", widthInPoints, heightInPoints));
+ }
+ return imageHandler(imageInfo);
+ }
+ }
+ catch (OutOfMemoryException)
+ {
+ // the Bitmap class can throw OutOfMemoryException, which means the bitmap is messed up, so punt.
+ return null;
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ }
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ return null;
+ }
+ }
+
+ private static float? HeightInPoints(IEnumerable<string> tokens)
+ {
+ return SizeInPoints(tokens, "height");
+ }
+
+ private static float? WidthInPoints(IEnumerable<string> tokens)
+ {
+ return SizeInPoints(tokens, "width");
+ }
+
+ private static float? SizeInPoints(IEnumerable<string> tokens, string name)
+ {
+ var sizeString = tokens
+ .Select(t => new
+ {
+ Name = t.Split(':').First(),
+ Value = t.Split(':').Skip(1).Take(1).FirstOrDefault()
+ })
+ .Where(p => p.Name == name)
+ .Select(p => p.Value)
+ .FirstOrDefault();
+
+ if (sizeString != null &&
+ sizeString.Length > 2 &&
+ sizeString.Substring(sizeString.Length - 2) == "pt")
+ {
+ float size;
+ if (float.TryParse(sizeString.Substring(0, sizeString.Length - 2), out size))
+ return size;
+ }
+ return null;
+ }
+
+ #endregion
+ }
+
+ public static class HtmlConverterExtensions
+ {
+ public static void AddIfMissing(this Dictionary<string, string> style, string propName, string value)
+ {
+ if (style.ContainsKey(propName))
+ return;
+ style.Add(propName, value);
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/WmlToXml.cs b/OpenXmlPowerTools/WmlToXml.cs
new file mode 100644
index 0000000..eae9a12
--- /dev/null
+++ b/OpenXmlPowerTools/WmlToXml.cs
@@ -0,0 +1,2003 @@
+/***************************************************************************
+
+Copyright (c) Eric White 2016. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+Published at http://EricWhite.com
+Resource Center and Documentation: http://ericwhite.com/
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.Drawing;
+
+namespace OpenXmlPowerTools
+{
+ public class ContentTypeRule
+ {
+ public string ContentType;
+ public string StyleName;
+ public Regex StyleNameRegex;
+ public Regex[] RegexArray;
+ public Func<XElement, ContentTypeRule, WordprocessingDocument, WmlToXmlSettings, bool> MatchLambda;
+ public bool ApplyRunContentTypes = true;
+ }
+
+ public class GlobalValidationRule
+ {
+ public string[] RuleNames;
+ public string[] RuleDescriptions;
+ public Func<GlobalValidationRule, WordprocessingDocument, WordprocessingDocument, XElement, WmlToXmlSettings, List<WmlToXmlValidationError>> GlobalRuleLambda;
+ public bool IsOnlyWarning;
+ public string Message;
+ }
+
+ public class BlockLevelContentValidationRule
+ {
+ public string[] RuleNames;
+ public string[] RuleDescriptions;
+ public Regex StyleNameRegex;
+ public Func<XElement, BlockLevelContentValidationRule, WordprocessingDocument, XElement, WmlToXmlSettings, List<WmlToXmlValidationError>> BlockLevelContentRuleLambda;
+ public bool IsOnlyWarning;
+ public string Message;
+ }
+
+ public class WmlToXmlValidationError
+ {
+ public string RuleName;
+ public string ErrorMessage;
+ public string BlockLevelContentIdentifier; // this string is the same as the unid that is in the source document. This string should be sufficient to identify and find any
+ // invalid paragraph, table, row, cell, or anything else in the source document.
+ // for now, i am putting an integer into this attribute / id, but I expect that this will be more elaborate than this.
+ // I need to again research exactly how to move to a specific paragraph or table in a document, in a VSTO app.
+ }
+
+ public class WmlToXmlProgressInfo
+ {
+ public int ContentCount;
+ public int ContentTotal;
+ public string InProgressMessage;
+ }
+
+ public class TransformInfo
+ {
+ public string DefaultLangFromStylesPart;
+ }
+
+ public class WmlToXmlSettings
+ {
+ public List<ContentTypeRule> GlobalContentTypeRules;
+ public List<ContentTypeRule> DocumentTypeContentTypeRules;
+ public List<ContentTypeRule> DocumentContentTypeRules;
+ public List<ContentTypeRule> RunContentTypeRules;
+ public List<GlobalValidationRule> GlobalValidationRules;
+ public List<BlockLevelContentValidationRule> BlockLevelContentValidationRules;
+ public ListItemRetrieverSettings ListItemRetrieverSettings;
+ public bool? InjectCommentForContentTypes;
+ public XElement ContentTypeHierarchyDefinition;
+ public Func<XElement, WmlToXmlSettings, bool> ContentTypeHierarchyLambda;
+ public Dictionary<string, Func<string, OpenXmlPart, XElement, WmlToXmlSettings, object>> XmlGenerationLambdas;
+ public DirectoryInfo ImageBase;
+ public bool WriteImageFiles = true;
+ public Action<WmlToXmlProgressInfo> ProgressFunction;
+ public XDocument ContentTypeRegexExtension;
+ public string DefaultLang;
+ public object UserData;
+
+ public WmlToXmlSettings(
+ List<ContentTypeRule> globalContentTypeRules,
+ List<ContentTypeRule> documentTypeContentTypeRules,
+ List<ContentTypeRule> documentContentTypeRules,
+ List<ContentTypeRule> runContentTypeRules,
+ List<GlobalValidationRule> globalValidationRules,
+ List<BlockLevelContentValidationRule> blockLevelContentValidationRules,
+ XElement contentTypeHierarchyDefinition,
+ Func<XElement, WmlToXmlSettings, bool> contentTypeHierarchyLambda,
+ Dictionary<string, Func<string, OpenXmlPart, XElement, WmlToXmlSettings, object>> xmlGenerationLambdas,
+ DirectoryInfo imageBase,
+ XDocument contentTypeRegexExtension)
+ {
+ GlobalContentTypeRules = globalContentTypeRules;
+ DocumentTypeContentTypeRules = documentTypeContentTypeRules;
+ DocumentContentTypeRules = documentContentTypeRules;
+ RunContentTypeRules = runContentTypeRules;
+ GlobalValidationRules = globalValidationRules;
+ BlockLevelContentValidationRules = blockLevelContentValidationRules;
+ ListItemRetrieverSettings = new ListItemRetrieverSettings();
+ ContentTypeHierarchyDefinition = contentTypeHierarchyDefinition;
+ ContentTypeHierarchyLambda = contentTypeHierarchyLambda;
+ XmlGenerationLambdas = xmlGenerationLambdas;
+ ImageBase = imageBase;
+ ContentTypeRegexExtension = contentTypeRegexExtension;
+ }
+
+ public WmlToXmlSettings(
+ List<ContentTypeRule> globalContentTypeRules,
+ List<ContentTypeRule> documentTypeContentTypeRules,
+ List<ContentTypeRule> documentContentTypeRules,
+ List<ContentTypeRule> runContentTypeRules,
+ List<GlobalValidationRule> globalValidationRules,
+ List<BlockLevelContentValidationRule> blockLevelContentValidationRules,
+ Func<XElement, WmlToXmlSettings, bool> contentTypeHierarchyLambda,
+ Dictionary<string, Func<string, OpenXmlPart, XElement, WmlToXmlSettings, object>> xmlGenerationLambdas,
+ ListItemRetrieverSettings listItemRetrieverSettings,
+ DirectoryInfo imageBase,
+ XDocument contentTypeRegexExtension)
+ {
+ GlobalContentTypeRules = globalContentTypeRules;
+ DocumentTypeContentTypeRules = documentTypeContentTypeRules;
+ DocumentContentTypeRules = documentContentTypeRules;
+ RunContentTypeRules = runContentTypeRules;
+ GlobalValidationRules = globalValidationRules;
+ BlockLevelContentValidationRules = blockLevelContentValidationRules;
+ ListItemRetrieverSettings = listItemRetrieverSettings;
+ ContentTypeHierarchyLambda = contentTypeHierarchyLambda;
+ XmlGenerationLambdas = xmlGenerationLambdas;
+ ImageBase = imageBase;
+ ContentTypeRegexExtension = contentTypeRegexExtension;
+ }
+ }
+
+ public static class WmlToXml
+ {
+ public static WmlDocument ApplyContentTypes(WmlDocument document, WmlToXmlSettings settings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ ApplyContentTypes(doc, settings);
+ }
+ return streamDoc.GetModifiedWmlDocument();
+ }
+ }
+
+ public static void ApplyContentTypes(WordprocessingDocument wDoc, WmlToXmlSettings settings)
+ {
+#if false
+<Extensions>
+ <Extension ContentType='Introduction'>
+ <RegexExtension>
+ <Regex>.*Infroduction.*</Regex>
+ <Regex>.*Entroduction.*</Regex>
+ </RegexExtension>
+ </Extension>
+</Extensions>
+#endif
+ if (settings.ContentTypeRegexExtension != null)
+ {
+ foreach (var ext in settings.ContentTypeRegexExtension.Root.Elements("Extension"))
+ {
+ var ct = (string)ext.Attribute("ContentType");
+ var rules = settings.DocumentContentTypeRules.Concat(settings.DocumentTypeContentTypeRules).Concat(settings.GlobalContentTypeRules);
+ var ruleToUpdate = rules
+ .FirstOrDefault(r => r.ContentType == ct);
+ if (ruleToUpdate == null)
+ throw new OpenXmlPowerToolsException("ContentTypeRexexExtension refers to content type that does not exist");
+ var oldRegexRules = ruleToUpdate.RegexArray.ToList();
+ var newRegexRules = ext.Elements("RegexExtension").Elements("Regex").Select(z => new Regex(z.Value)).ToArray();
+ var regexArray = oldRegexRules.Concat(newRegexRules).ToArray();
+ ruleToUpdate.RegexArray = regexArray;
+ }
+ }
+
+ if (settings.ProgressFunction != null)
+ {
+ WmlToXmlProgressInfo pi = new WmlToXmlProgressInfo()
+ {
+ ContentCount = 0,
+ ContentTotal = 0,
+ InProgressMessage = "Simplify markup" + Environment.NewLine,
+ };
+ settings.ProgressFunction(pi);
+ }
+
+ SimplifyMarkupSettings markupSimplifierSettings = new SimplifyMarkupSettings()
+ {
+ AcceptRevisions = true,
+ NormalizeXml = true,
+ RemoveBookmarks = false,
+ RemoveComments = true,
+ RemoveContentControls = false,
+ RemoveEndAndFootNotes = false,
+ RemoveFieldCodes = false,
+ RemoveGoBackBookmark = true,
+ RemoveHyperlinks = false,
+ RemoveLastRenderedPageBreak = true,
+ RemoveMarkupForDocumentComparison = false,
+ RemovePermissions = true,
+ RemoveProof = true,
+ RemoveRsidInfo = true,
+ RemoveSmartTags = true,
+ RemoveSoftHyphens = false,
+ RemoveWebHidden = true,
+ ReplaceTabsWithSpaces = false,
+ };
+ MarkupSimplifier.SimplifyMarkup(wDoc, markupSimplifierSettings);
+
+ if (settings.ProgressFunction != null)
+ {
+ WmlToXmlProgressInfo pi = new WmlToXmlProgressInfo()
+ {
+ ContentCount = 0,
+ ContentTotal = 0,
+ InProgressMessage = "Assemble formatting" + Environment.NewLine,
+ };
+ settings.ProgressFunction(pi);
+ }
+
+ FormattingAssemblerSettings formattingAssemblerSettings = new FormattingAssemblerSettings();
+ formattingAssemblerSettings.RemoveStyleNamesFromParagraphAndRunProperties = false;
+ formattingAssemblerSettings.RestrictToSupportedLanguages = false;
+ formattingAssemblerSettings.RestrictToSupportedNumberingFormats = false;
+ FormattingAssembler.AssembleFormatting(wDoc, formattingAssemblerSettings);
+
+ ContentTypeApplierInfo ctai = new ContentTypeApplierInfo();
+
+ XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ XElement defaultParagraphStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true &&
+ (string)st.Attribute(W.type) == "paragraph");
+ if (defaultParagraphStyle != null)
+ ctai.DefaultParagraphStyleName = (string)defaultParagraphStyle.Attribute(W.styleId);
+ XElement defaultCharacterStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true &&
+ (string)st.Attribute(W.type) == "character");
+ if (defaultCharacterStyle != null)
+ ctai.DefaultCharacterStyleName = (string)defaultCharacterStyle.Attribute(W.styleId);
+ XElement defaultTableStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true &&
+ (string)st.Attribute(W.type) == "table");
+ if (defaultTableStyle != null)
+ ctai.DefaultTableStyleName = (string)defaultTableStyle.Attribute(W.styleId);
+
+ if (settings.ProgressFunction != null)
+ {
+ WmlToXmlProgressInfo pi = new WmlToXmlProgressInfo()
+ {
+ ContentCount = 0,
+ ContentTotal = 0,
+ InProgressMessage = "Assemble list item information" + Environment.NewLine,
+ };
+ settings.ProgressFunction(pi);
+ }
+
+ ListItemRetrieverSettings listItemRetrieverSettings = new ListItemRetrieverSettings();
+ AssembleListItemInformation(wDoc, settings.ListItemRetrieverSettings);
+ ApplyContentTypesForRuleSet(settings, ctai, wDoc);
+ }
+
+ public static XElement ProduceContentTypeXml(WmlDocument document, WmlToXmlSettings settings)
+ {
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document))
+ {
+ using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
+ {
+ return ProduceContentTypeXml(doc, settings);
+ }
+ }
+ }
+
+ public static XElement ProduceContentTypeXml(WordprocessingDocument wDoc, WmlToXmlSettings settings)
+ {
+ var mainPart = wDoc.MainDocumentPart;
+ var mainXDoc = mainPart.GetXDocument();
+
+#if false
+<w:styles xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 w16se">
+ <w:docDefaults>
+ <w:rPrDefault>
+ <w:rPr>
+ <w:rFonts w:ascii="Georgia" w:eastAsiaTheme="minorHAnsi" w:hAnsi="Georgia" w:cs="Times New Roman"/>
+ <w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA"/>
+ </w:rPr>
+ </w:rPrDefault>
+ <w:pPrDefault/>
+ </w:docDefaults>
+#endif
+
+ AssignLevelsToContent(mainXDoc, settings);
+
+ // Call RetrieveListItem so that all paragraphs are initialized with ListItemInfo
+ var firstParagraph = mainXDoc.Descendants(W.p).FirstOrDefault();
+
+ // if there is no content, then return an empty document.
+ if (firstParagraph == null)
+ return new XElement("ContentTypeXml");
+
+ var listItem = ListItemRetriever.RetrieveListItem(wDoc, firstParagraph);
+
+ // Annotate runs associated with fields, so that can retrieve hyperlinks that are stored as fields.
+ FieldRetriever.AnnotateWithFieldInfo(wDoc.MainDocumentPart);
+
+ AnnotateRunsThatUseFieldsForNumbering(mainXDoc);
+
+ var newRoot = (XElement)AnnotateRunsThatUseFldSimple(mainXDoc.Root);
+ mainXDoc.Root.ReplaceWith(newRoot);
+
+ wDoc.MainDocumentPart.PutXDocument();
+
+ // Annotate runs associated with fields, so that can retrieve hyperlinks that are stored as fields.
+ FieldRetriever.AnnotateWithFieldInfo(wDoc.MainDocumentPart);
+
+ mainXDoc = wDoc.MainDocumentPart.GetXDocument();
+
+ var body = mainXDoc.Root.Descendants(W.body).FirstOrDefault();
+ if (body == null)
+ throw new OpenXmlPowerToolsException("Internal error: invalid document");
+
+ var contentList = body.Elements()
+ .Where(e => e.Attribute(PtOpenXml.Level) != null)
+ .ToList();
+
+ var rootLevelContentList = contentList
+ .Where(h => (int)h.Attribute(PtOpenXml.Level) == 1)
+ .ToList();
+
+ var contentTypeXml = new XElement("ContentTypeXml",
+ rootLevelContentList
+ .Select(h =>
+ {
+ var childrenHeadings = GetChildrenHeadings(mainPart, contentList, h, settings);
+ XElement xml = (XElement)ProduceXmlTransform(mainPart, h, settings);
+ if (xml != null)
+ xml.Add(childrenHeadings);
+ return xml;
+ }));
+
+ contentTypeXml = HierarchyPerSettings(contentTypeXml, settings);
+
+ return contentTypeXml;
+ }
+
+ private static XElement HierarchyPerSettings(XElement contentTypeXml, WmlToXmlSettings settings)
+ {
+ var hierarchyDefinition = settings.ContentTypeHierarchyDefinition;
+ HashSet<XName> hierarchyElements = new HashSet<XName>(hierarchyDefinition.DescendantsAndSelf().Select(d => d.Name).Distinct());
+
+ Stack<XElement> stack = new Stack<XElement>();
+ var rootElement = hierarchyDefinition
+ .Elements()
+ .FirstOrDefault(e => (bool)e.Attribute("IsRoot"));
+ if (rootElement == null)
+ throw new OpenXmlPowerToolsException("Invalid content type hierarchy definition - no root element");
+ stack.Push(rootElement);
+
+ var currentlyLookingAt = hierarchyDefinition.Element(rootElement.Name);
+
+ foreach (var item in contentTypeXml.Elements())
+ {
+ if (!hierarchyElements.Contains(item.Name))
+ throw new OpenXmlPowerToolsException(string.Format("Invalid Content Type Hierarchy Definition - missing def for {0}", item.Name));
+
+ bool found = false;
+ var possibleChildItem = currentlyLookingAt.Element(item.Name);
+ if (possibleChildItem != null)
+ {
+ if (!possibleChildItem.HasAttributes)
+ found = true;
+ if (!found)
+ {
+ var anyMismatch = possibleChildItem.Attributes().Any(a =>
+ {
+ var val1 = a.Value;
+ var a2 = item.Attribute(a.Name);
+ if (a2 == null)
+ return true;
+ var val2 = a2.Value;
+ if (val1 != val2)
+ return true;
+ return false;
+ });
+ if (!anyMismatch)
+ found = true;
+ }
+ }
+ if (found)
+ {
+ item.Add(new XAttribute(PtOpenXml.IndentLevel, stack.Count()));
+ stack.Push(item);
+ currentlyLookingAt = FindCurrentlyLookingAt(hierarchyDefinition, item);
+ continue;
+ }
+ if (hierarchyElements.Contains(item.Name))
+ {
+ while (true)
+ {
+ if (stack.Count() == 1)
+ {
+ // have encountered an unexpected hierarchy element. have gone up the stack, and no element up the stack allows for this as a child element.
+ // Therefore, put it at level one, and let the Narrdoc transform generate invalid narrdoc.
+ item.Add(new XAttribute(PtOpenXml.IndentLevel, stack.Count()));
+ break;
+ }
+ stack.Pop();
+ var last = stack.Peek();
+ currentlyLookingAt = FindCurrentlyLookingAt(hierarchyDefinition, last);
+ bool found2 = false;
+ var possibleChildItem2 = currentlyLookingAt.Element(item.Name);
+ if (possibleChildItem2 != null)
+ {
+ if (!possibleChildItem2.HasAttributes)
+ found2 = true;
+ if (!found2)
+ {
+ var anyMismatch2 = possibleChildItem2.Attributes().Any(a =>
+ {
+ var val1 = a.Value;
+ var a2 = item.Attribute(a.Name);
+ if (a2 == null)
+ return true;
+ var val2 = a2.Value;
+ if (val1 != val2)
+ return true;
+ return false;
+ });
+ if (!anyMismatch2)
+ found2 = true;
+ }
+ }
+ if (found2)
+ {
+ item.Add(new XAttribute(PtOpenXml.IndentLevel, stack.Count()));
+ stack.Push(item);
+ currentlyLookingAt = FindCurrentlyLookingAt(hierarchyDefinition, item);
+ break;
+ }
+ if (stack.Count() == 0)
+ throw new OpenXmlPowerToolsException("Internal error = reached top of hierarchy - prob not an internal error - some other error");
+ }
+ continue;
+ }
+ // otherwise continue on to next item.
+ }
+
+ var hierarchicalContentTypeXml = new XElement("ContentTypeXml",
+ HierarchyPerSettingsTransform(contentTypeXml.Elements(), 1));
+
+ hierarchicalContentTypeXml.DescendantsAndSelf().Attributes(PtOpenXml.IndentLevel).Remove();
+
+ return hierarchicalContentTypeXml;
+ }
+
+ private static XElement FindCurrentlyLookingAt(XElement hierarchyDefinition, XElement item)
+ {
+ var candidates = hierarchyDefinition
+ .Elements(item.Name)
+ .OrderByDescending(e => e.Attributes().Count());
+
+ var theOne = candidates
+ .FirstOrDefault(c =>
+ {
+ if (!c.HasAttributes)
+ return true;
+ var anyMismatch2 = c.Attributes().Any(a =>
+ {
+ var val1 = a.Value;
+ var a2 = item.Attribute(a.Name);
+ if (a2 == null)
+ return true;
+ var val2 = a2.Value;
+ if (val1 != val2)
+ return true;
+ return false;
+ });
+ if (anyMismatch2)
+ return false;
+ return true;
+ });
+
+ if (theOne == null)
+ throw new OpenXmlPowerToolsException("Internal error");
+
+ return theOne;
+ }
+
+ private static object HierarchyPerSettingsTransform(IEnumerable<XElement> list, int level)
+ {
+ // small optimization - other code in this method would have same effect, but this is more efficient.
+ if (!list.Any())
+ return null;
+
+ List<int> groupingKeys = new List<int>();
+ int currentGroupingKey = 0;
+ foreach (var item in list)
+ {
+ if (item.Attribute(PtOpenXml.IndentLevel) == null)
+ throw new OpenXmlPowerToolsException(string.Format("Invalid Content Type Hierarchy Definition - missing def for {0}", item.Name));
+ if ((int)item.Attribute(PtOpenXml.IndentLevel) == level)
+ {
+ currentGroupingKey += 1;
+ }
+ groupingKeys.Add(currentGroupingKey);
+ }
+
+ var zipped = list
+ .Zip(groupingKeys, (item, key) => new
+ {
+ Item = item,
+ Key = key,
+ })
+ .GroupBy(z => z.Key)
+ .ToList();
+
+ var newContent = zipped
+ .Select(z =>
+ {
+ var first = z.First().Item;
+ var newItem = new XElement(first.Name,
+ first.Attributes(),
+ first.Elements(),
+ HierarchyPerSettingsTransform(z.Skip(1).Select(r => r.Item), level + 1));
+ return newItem;
+ })
+ .ToList();
+
+ return newContent;
+ }
+
+
+ // this is where we need to do the same type of run annotation as for complex fields, but for simple fields.
+ // I think that we may need to split up the run following the simple field
+
+#if false
+<w:p pt:StyleName="Caption" pt:ContentType="Caption" pt:Level="2">
+ <w:r pt:ContentType="Span">
+ <w:t xml:space="preserve">Table </w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="begin" />
+ </w:r>
+ <w:r>
+ <w:instrText xml:space="preserve"> STYLEREF 1 \s </w:instrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="separate" />
+ </w:r>
+ <w:r pt:ContentType="Span">
+ <w:t>1</w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="end" />
+ </w:r>
+ <w:r pt:ContentType="Span">
+ <w:t>.</w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="begin" />
+ </w:r>
+ <w:r>
+ <w:instrText xml:space="preserve"> SEQ Table \* ARABIC </w:instrText>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="separate" />
+ </w:r>
+ <w:r pt:ContentType="Span">
+ <w:t>1</w:t>
+ </w:r>
+ <w:r>
+ <w:fldChar w:fldCharType="end" />
+ </w:r>
+ <w:r pt:ContentType="Span">
+ <w:t>Type the title here</w:t>
+ </w:r>
+</w:p>
+#endif
+
+ private static void AnnotateRunsThatUseFieldsForNumbering(XDocument mainXDoc)
+ {
+ var cachedAnnotationInformation = mainXDoc.Root.Annotation<Dictionary<int, List<XElement>>>();
+ if (cachedAnnotationInformation == null)
+ return;
+
+ StringBuilder sb = new StringBuilder();
+ foreach (var item in cachedAnnotationInformation)
+ {
+ var instrText = FieldRetriever.InstrText(mainXDoc.Root, item.Key).TrimStart('{').TrimEnd('}');
+ var fi = FieldRetriever.ParseField(instrText);
+
+ if (fi.FieldType.ToUpper() == "SEQ" || fi.FieldType.ToUpper() == "STYLEREF")
+ {
+ var runsForField = mainXDoc
+ .Root
+ .Descendants()
+ .Where(d =>
+ {
+ Stack<FieldRetriever.FieldElementTypeInfo> stack = d.Annotation<Stack<FieldRetriever.FieldElementTypeInfo>>();
+ if (stack == null)
+ return false;
+ if (stack.Any(stackItem => stackItem.Id == item.Key && stackItem.FieldElementType == FieldRetriever.FieldElementTypeEnum.Result))
+ return true;
+ return false;
+ })
+ .Select(d => d.AncestorsAndSelf(W.r).FirstOrDefault())
+ .Where(z9 => z9 != null)
+ .GroupAdjacent(o => o)
+ .Select(g => g.First())
+ .Where(r => r.Element(W.t) != null)
+ .ToList();
+
+ if (!runsForField.Any())
+ continue;
+
+ var lastRun = runsForField.LastOrDefault();
+
+ var para = lastRun
+ .Ancestors(W.p)
+ .FirstOrDefault();
+
+ if (para == null)
+ throw new OpenXmlPowerToolsException("Internal error - invalid document");
+
+ // if already processed
+ if (para.Descendants(W.r).Any(r => r.Attribute(PtOpenXml.ListItemRun) != null))
+ continue;
+
+ var lastFldCharRun = para
+ .Elements(W.r)
+ .LastOrDefault(r =>
+ {
+ if (r.Element(W.fldChar) == null)
+ return false;
+ Stack<FieldRetriever.FieldElementTypeInfo> stack = r.Annotation<Stack<FieldRetriever.FieldElementTypeInfo>>();
+ if (stack == null)
+ return false;
+
+ if (stack.Any(stackItem =>
+ {
+ var instrText2 = FieldRetriever.InstrText(mainXDoc.Root, stackItem.Id).TrimStart('{').TrimEnd('}');
+ var fi2 = FieldRetriever.ParseField(instrText2);
+ if (fi2.FieldType.ToUpper() == "SEQ" || fi2.FieldType.ToUpper() == "STYLEREF")
+ return true;
+ return false;
+ }))
+ return true;
+ return false;
+ });
+
+ var elementAfter = lastFldCharRun
+ .ElementsAfterSelf(W.r)
+ .FirstOrDefault();
+
+ // elementAfter may be null - that is ok - the rest of the routine works properly in this case.
+
+ var listItemText = para
+ .Elements(W.r)
+ .TakeWhile(e => e != elementAfter)
+ .Select(r1 => r1.Descendants(W.t).Select(t => (string)t).StringConcatenate())
+ .StringConcatenate()
+ .Trim();
+
+ var nextRun = lastFldCharRun
+ .ElementsAfterSelf(W.r)
+ .FirstOrDefault(nr => nr.Element(W.t) != null);
+
+ var lastFldCharRunText = lastFldCharRun
+ .ElementsBeforeSelf(W.r)
+ .Reverse()
+ .First(r => r.Element(W.t) != null)
+ .Element(W.t);
+
+ string sepCharsString = "";
+ if (nextRun != null)
+ {
+ var nextRunTextElement = nextRun
+ .Element(W.t);
+
+ var nextRunText = nextRunTextElement.Value;
+ var sepChars = nextRunText
+ .TakeWhile(ch => ch == '.' || ch == ' ')
+ .ToList();
+
+ sepCharsString = nextRunText.Substring(0, sepChars.Count());
+
+ nextRunText = nextRunText.Substring(sepChars.Count());
+ nextRunTextElement.Value = nextRunText;
+
+ lastFldCharRunText.Value = lastFldCharRunText.Value + sepCharsString;
+ }
+
+ Regex re = new Regex("[A-F0-9.]+$");
+ Match m = re.Match(listItemText);
+ string matchedValue = null;
+ if (m.Success)
+ {
+ matchedValue = m.Value;
+ }
+
+ if (matchedValue != null)
+ {
+ matchedValue += sepCharsString;
+ matchedValue = matchedValue.TrimStart('.');
+ matchedValue = matchedValue.TrimEnd('.', ' ');
+
+ foreach (var run in para.Elements(W.r).TakeWhile(e => e != elementAfter).Where(e => e.Element(W.t) != null))
+ run.Add(new XAttribute(PtOpenXml.ListItemRun, matchedValue));
+ }
+
+ }
+
+#if false
+ // old code
+ if (fi.FieldType.ToUpper() == "SEQ")
+ {
+ // have it
+
+ var runsForField = mainXDoc
+ .Root
+ .Descendants()
+ .Where(d =>
+ {
+ Stack<FieldRetriever.FieldElementTypeInfo> stack = d.Annotation<Stack<FieldRetriever.FieldElementTypeInfo>>();
+ if (stack == null)
+ return false;
+ if (stack.Any(stackItem => stackItem.Id == item.Key && stackItem.FieldElementType == FieldRetriever.FieldElementTypeEnum.Result))
+ return true;
+ return false;
+ })
+ .Select(d => d.AncestorsAndSelf(W.r).FirstOrDefault())
+ .Where(z9 => z9 != null)
+ .GroupAdjacent(o => o)
+ .Select(g => g.First())
+ .Where(r => r.Element(W.t) != null)
+ .ToList();
+
+ if (!runsForField.Any())
+ continue;
+
+ var lastRun = runsForField
+ .Last();
+
+ var lastRunTextElement = lastRun
+ .Element(W.t);
+
+ var lastRunText = lastRunTextElement.Value;
+
+ var nextRun = lastRun
+ .ElementsAfterSelf(W.r)
+ .FirstOrDefault(r => r.Element(W.t) != null);
+
+ if (nextRun != null)
+ {
+ var nextRunTextElement = nextRun
+ .Element(W.t);
+
+ var nextRunText = nextRunTextElement.Value;
+ var sepChars = nextRunText
+ .TakeWhile(ch => ch == '.' || ch == ' ')
+ .ToList();
+
+ nextRunText = nextRunText.Substring(sepChars.Count());
+ nextRunTextElement.Value = nextRunText;
+
+ lastRunText = lastRunTextElement.Value + sepChars.Select(ch => ch.ToString()).StringConcatenate();
+ lastRunTextElement.Value = lastRunText;
+ }
+
+ lastRun.Add(new XAttribute(PtOpenXml.ListItemRun, lastRunText));
+
+ foreach (var runbefore in lastRun
+ .ElementsBeforeSelf(W.r)
+ .Where(rz => rz.Element(W.t) != null))
+ {
+ runbefore.Add(new XAttribute(PtOpenXml.ListItemRun, lastRunText));
+ }
+ }
+#endif
+
+ }
+ }
+
+#if false
+<w:p pt14:StyleName="Caption">
+ <w:r>
+ <w:t xml:space="preserve">Box </w:t>
+ </w:r>
+ <w:fldSimple w:instr=" SEQ Box \* ARABIC ">
+ <w:r>
+ <w:t>1</w:t>
+ </w:r>
+ </w:fldSimple>
+ <w:r>
+ <w:t>. Type the title here</w:t>
+ </w:r>
+</w:p>
+#endif
+ private static object AnnotateRunsThatUseFldSimple(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p &&
+ element.Elements(W.fldSimple).Any(fs =>
+ {
+ var instrText = ((string)fs.Attribute(W.instr)).Trim();
+ return instrText.StartsWith("SEQ");
+ }))
+ {
+ var fldSimple = element.Elements(W.fldSimple).FirstOrDefault(fs =>
+ {
+ var instrText = ((string)fs.Attribute(W.instr)).Trim();
+ return instrText.StartsWith("SEQ");
+ });
+ var instr = ((string)fldSimple.Attribute(W.instr)).Trim();
+
+ // we have to do some funny business here because Word puts the ". " as part of the text following the fldSimple, and we want that text to be part of the list item.
+ var runAfter = fldSimple.ElementsAfterSelf(W.r).FirstOrDefault();
+ var runAfterText = runAfter.Elements(W.t).Select(t => (string)t).StringConcatenate();
+ var runAfterTextTrimmed = runAfterText.TrimStart('.', ' ');
+ var listItemNum = fldSimple.Elements(W.r).Elements(W.t).Select(t => (string)t).StringConcatenate();
+ var runsBefore = element
+ .Elements()
+ .TakeWhile(fs => fs.Name != W.fldSimple || (fs.Name == W.fldSimple && !((string)fs.Attribute(W.instr)).Trim().StartsWith("SEQ")))
+ .Select(e =>
+ {
+#if false
+<w:r pt14:StyleName="DefaultParagraphFont" pt14:FontName="Calibri" pt14:LanguageType="western" pt14:ListItemRun="3" xmlns:pt14="http://powertools.codeplex.com/2011" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+ <w:rPr>
+ <w:rFonts w:asciiTheme="minorHAnsi" w:hAnsiTheme="minorHAnsi" w:eastAsiaTheme="minorHAnsi" w:cstheme="minorBidi" w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Calibri" w:cs="" />
+ <w:bCs />
+ <w:sz w:val="22" />
+ <w:szCs w:val="22" />
+ <w:lang w:bidi="ar-SA" w:eastAsia="en-US" w:val="en-US" />
+ </w:rPr>
+ <w:t>3.</w:t>
+</w:r>
+#endif
+ var newE = new XElement(e); // clone
+ if (e.Value != "" && e.Attribute(PtOpenXml.ListItemRun) == null)
+ newE.Add(new XAttribute(PtOpenXml.ListItemRun, listItemNum));
+ return newE;
+ })
+ .ToList();
+ var fldSimpleRuns = fldSimple.Elements().Select(e =>
+ {
+ var newE = new XElement(e.Name,
+ e.Attributes(),
+ new XAttribute(PtOpenXml.ListItemRun, listItemNum),
+ e.Elements());
+ return newE;
+ });
+ var runAfterTextTrimmedLength = runAfterText.Length - runAfterTextTrimmed.Length;
+ XElement runAfterListItemElement = null;
+ if (runAfterTextTrimmedLength != 0)
+ {
+ runAfterListItemElement = new XElement(W.r,
+ runAfter.Attributes(),
+ new XAttribute(PtOpenXml.ListItemRun, listItemNum),
+ runAfter.Elements(W.rPr),
+ new XElement(W.t, runAfterText.Substring(0, runAfterTextTrimmedLength)));
+ }
+ XElement runAfterRemainderElement = new XElement(W.r,
+ runAfter.Attributes(),
+ runAfter.Elements(W.rPr),
+ new XElement(W.t, runAfterText.Substring(runAfterTextTrimmedLength)));
+ var newPara = new XElement(W.p,
+ element.Attributes(),
+ runsBefore,
+ fldSimpleRuns,
+ runAfterListItemElement,
+ runAfterRemainderElement,
+ fldSimple.ElementsAfterSelf(W.r).Skip(1));
+ return newPara;
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => AnnotateRunsThatUseFldSimple(n)));
+ }
+ return node;
+ }
+
+ // this method produces the XML for an endnote or footnote - the blockLevelContentContainer is the w:endnote or w:footnote element, and it produces the content type XML for the
+ // contents of the endnote or footnote, to be inserted en situ in the ContentTypeXml.
+ public static object ProduceContentTypeXmlForBlockLevelContentContainer(WordprocessingDocument wDoc, WmlToXmlSettings settings, OpenXmlPart part, XElement blockLevelContentContainer)
+ {
+ AssignLevelsToContentForEndFootNote(blockLevelContentContainer, settings);
+
+ // Call RetrieveListItem so that all paragraphs are initialized with ListItemInfo
+ var firstParagraph = blockLevelContentContainer.Descendants(W.p).FirstOrDefault();
+ var listItem = ListItemRetriever.RetrieveListItem(wDoc, firstParagraph);
+
+ var contentList = blockLevelContentContainer.Elements()
+ .Where(e => e.Attribute(PtOpenXml.Level) != null)
+ .ToList();
+
+ var rootLevelContentList = contentList
+ .Where(h => (int)h.Attribute(PtOpenXml.Level) == 1)
+ .ToList();
+
+ var contentTypeXml = rootLevelContentList
+ .Select(h =>
+ {
+ var childrenHeadings = GetChildrenHeadings(part, contentList, h, settings);
+ XElement xml = (XElement)ProduceXmlTransform(part, h, settings);
+ if (xml != null)
+ xml.Add(childrenHeadings);
+ return xml;
+ });
+
+ return contentTypeXml;
+ }
+
+
+ private static object GetChildrenHeadings(OpenXmlPart part, List<XElement> contentList, XElement parent, WmlToXmlSettings settings)
+ {
+ return contentList
+ .SkipWhile(h => h != parent)
+ .Skip(1)
+ .TakeWhile(h => (int)h.Attribute(PtOpenXml.Level) > (int)parent.Attribute(PtOpenXml.Level))
+ .Where(h => (int)h.Attribute(PtOpenXml.Level) == (int)parent.Attribute(PtOpenXml.Level) + 1)
+ .Select(h =>
+ {
+ var childrenHeadings = GetChildrenHeadings(part, contentList, h, settings);
+ XElement xml = (XElement)ProduceXmlTransform(part, h, settings);
+ if (xml != null)
+ xml.Add(childrenHeadings);
+ return xml;
+ }
+ );
+ }
+
+ public static object ProduceXmlTransform(OpenXmlPart part, XNode node, WmlToXmlSettings settings)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (settings.XmlGenerationLambdas == null)
+ throw new ArgumentOutOfRangeException("Xml Generation Lambdas are required");
+
+ var contentType = (string)element.Attribute(PtOpenXml.ContentType);
+
+ if (element.Name == W.t || element.Name == W.fldSimple)
+ return element.Nodes().Select(z => ProduceXmlTransform(part, z, settings));
+
+ if (contentType == null && element.Name == W.r)
+ {
+ if (settings.XmlGenerationLambdas.ContainsKey("Run"))
+ {
+ var lamda = settings.XmlGenerationLambdas["Run"];
+ var newElement = lamda(contentType, part, element, settings);
+ return newElement;
+ }
+ else
+ {
+ throw new OpenXmlPowerToolsException("Entry for Run content type in XML generation lambdas is required");
+ }
+ }
+
+ if (element.Name == W.hyperlink)
+ {
+ if (settings.XmlGenerationLambdas.ContainsKey("Hyperlink"))
+ {
+ var lamda = settings.XmlGenerationLambdas["Hyperlink"];
+ var newElement = lamda(contentType, part, element, settings);
+ return newElement;
+ }
+ else
+ {
+ throw new OpenXmlPowerToolsException("Entry for Hyperlink content type in XML generation lambdas is required");
+ }
+ }
+
+ if (contentType != null)
+ {
+
+ if (settings.XmlGenerationLambdas != null)
+ {
+ if (settings.XmlGenerationLambdas.ContainsKey(contentType))
+ {
+ var lamda = settings.XmlGenerationLambdas[contentType];
+ var newElement = lamda(contentType, part, element, settings);
+
+ string lang = (string)element.Elements(W.pPr).Elements(W.rPr).Elements(W.lang).Attributes(W.val).FirstOrDefault();
+ if (lang == null)
+ lang = settings.DefaultLang;
+ if (lang != null && ! lang.StartsWith("en")) // TODO we are not generating lang if English, but this needs revised after analysis
+ {
+ var n = newElement as XElement;
+ if (n != null)
+ {
+ n.Add(new XAttribute("Lang", lang));
+ if (element.Attribute(PtOpenXml.Unid) != null)
+ n.Add(new XAttribute("Unid", element.Attribute(PtOpenXml.Unid).Value));
+ return n;
+ }
+ }
+
+ var n2 = newElement as XElement;
+ if (n2 != null && element.Attribute(PtOpenXml.Unid) != null)
+ {
+ n2.Add(new XAttribute("Unid", element.Attribute(PtOpenXml.Unid).Value));
+ return n2;
+ }
+
+ return newElement;
+ }
+
+ }
+
+ // if no generation rules are set, or if there is no rule for this content type, then
+ // generate the default, for now.
+
+ // todo this is not ideal in my mind. Need to think about this more. Maybe every content type
+ // must have a generation lambda.
+
+ return new XElement(contentType, new XElement("Content",
+ element.Elements().Select(rce => ProduceXmlTransform(part, rce, settings))));
+ }
+
+ // ignore any other elements
+ return null;
+ }
+
+#if false
+ // The following code inserts an XML comment for unicode characters above 256
+
+ // This could be made more efficient - group characters together and create fewer XText nodes.
+ // As it is, it is pretty slow, so should be used only for debugging.
+
+ var xt = node as XText;
+ if (xt != null)
+ {
+ var newContent = xt.Value.Select(c =>
+ {
+ var ic = (int)c;
+ if (ic < 256)
+ return (object)new XText(c.ToString());
+
+ return new[] {
+ (object)new XText(c.ToString()),
+ new XComment(ic.ToString("X")),
+ };
+ })
+ .ToList();
+ return newContent;
+ }
+#endif
+
+ return node;
+ }
+
+ private static void AssignLevelsToContent(XDocument mainXDoc, WmlToXmlSettings settings)
+ {
+ var contentWithContentType = mainXDoc
+ .Root
+ .Descendants()
+ .Where(d => d.Name == W.p || d.Name == W.tbl || d.Name == W.tr || d.Name == W.tc)
+ .Where(d => d.Attribute(PtOpenXml.ContentType) != null)
+ .ToList();
+
+ int currentLevel = 1;
+ foreach (var content in contentWithContentType)
+ {
+ var thisLevel = GetIndentLevel(content, settings);
+ if (thisLevel == null)
+ {
+ content.Add(new XAttribute(PtOpenXml.Level, currentLevel));
+ }
+ else
+ {
+ if (content.Attribute(PtOpenXml.Level) == null)
+ content.Add(new XAttribute(PtOpenXml.Level, thisLevel));
+ currentLevel = (int)thisLevel + 1;
+ }
+ }
+ }
+
+ private static void AssignLevelsToContentForEndFootNote(XElement blockLevelContentContainer, WmlToXmlSettings settings)
+ {
+ var contentWithContentType = blockLevelContentContainer
+ .Descendants()
+ .Where(d => d.Name == W.p || d.Name == W.tbl || d.Name == W.tr || d.Name == W.tc)
+ .Where(d => d.Attribute(PtOpenXml.ContentType) != null)
+ .ToList();
+
+ foreach (var content in contentWithContentType)
+ content.Add(new XAttribute(PtOpenXml.Level, 1));
+ }
+
+ private static int? GetIndentLevel(XElement blockLevelContent, WmlToXmlSettings settings)
+ {
+ if (settings.ContentTypeHierarchyLambda(blockLevelContent, settings))
+ return 1;
+ return 2;
+ }
+
+ // Apply the Document rules first, then apply the DocumentType rules, then apply the Global rules. First one that matches, wins.
+ private static void ApplyContentTypesForRuleSet(WmlToXmlSettings settings, ContentTypeApplierInfo ctai, WordprocessingDocument wDoc)
+ {
+ ApplyRulesToPart(settings, ctai, wDoc, wDoc.MainDocumentPart);
+ if (wDoc.MainDocumentPart.EndnotesPart != null)
+ ApplyRulesToPart(settings, ctai, wDoc, wDoc.MainDocumentPart.EndnotesPart);
+ if (wDoc.MainDocumentPart.FootnotesPart != null)
+ ApplyRulesToPart(settings, ctai, wDoc, wDoc.MainDocumentPart.FootnotesPart);
+ }
+
+ private static void ApplyRulesToPart(WmlToXmlSettings settings, ContentTypeApplierInfo ctai, WordprocessingDocument wDoc, OpenXmlPart part)
+ {
+ var partXDoc = part.GetXDocument();
+ var styleXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+ var blockContent = partXDoc.Descendants()
+ .Where(d => d.Name == W.p || d.Name == W.tbl || d.Name == W.tr || d.Name == W.tc);
+
+ int totalCount = 0;
+ if (settings.ProgressFunction != null)
+ {
+ totalCount = blockContent.Count();
+ string message;
+ if (part is MainDocumentPart)
+ message = "Apply rules to main document part";
+ else if (part is EndnotesPart)
+ message = "Apply rules to endnotes part";
+ else
+ message = "Apply rules to footnotes part";
+ WmlToXmlProgressInfo pi = new WmlToXmlProgressInfo()
+ {
+ ContentTotal = totalCount,
+ ContentCount = 0,
+ InProgressMessage = message + Environment.NewLine,
+ };
+ settings.ProgressFunction(pi);
+ }
+
+ var count = 0;
+ foreach (var blc in blockContent)
+ {
+ if (settings.ProgressFunction != null)
+ {
+ ++count;
+ if (count < 50 || (count) % 10 == 0 || count == totalCount)
+ {
+ var msg = string.Format(" {0} of {1}", count, totalCount);
+ msg += "".PadRight(msg.Length, '\b');
+ WmlToXmlProgressInfo pi2 = new WmlToXmlProgressInfo()
+ {
+ ContentTotal = totalCount,
+ ContentCount = count,
+ InProgressMessage = msg,
+ };
+ settings.ProgressFunction(pi2);
+ }
+ }
+
+ string styleOfBlc = null;
+ string styleOfBlcUC = null;
+ if (blc.Name == W.p)
+ {
+ var styleIdOfBlc = (string)blc.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault();
+ if (styleIdOfBlc != null)
+ {
+ styleOfBlc = (string)styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(s => (string)s.Attribute(W.styleId) == styleIdOfBlc && (string)s.Attribute(W.type) == "paragraph")
+ .Elements(W.name)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ }
+ if (styleOfBlc == null)
+ styleOfBlc = ctai.DefaultParagraphStyleName;
+ styleOfBlcUC = styleOfBlc.ToUpper();
+ }
+ else if (blc.Name == W.tbl)
+ {
+ var styleIdOfBlc = (string)blc.Elements(W.tblPr).Elements(W.tblStyle).Attributes(W.val).FirstOrDefault();
+ if (styleIdOfBlc != null)
+ {
+ styleOfBlc = (string)styleXDoc
+ .Root
+ .Elements(W.style)
+ .Where(s => (string)s.Attribute(W.styleId) == styleIdOfBlc && (string)s.Attribute(W.type) == "table")
+ .Elements(W.name)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ }
+ if (styleOfBlc == null)
+ styleOfBlc = ctai.DefaultTableStyleName;
+ styleOfBlcUC = styleOfBlc.ToUpper();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+ // The following is useful to get a list of all content types and the code gen list
+
+ //var contentTypeList = settings
+ // .DocumentContentTypeRules
+ // .Concat(settings.DocumentTypeContentTypeRules)
+ // .Concat(settings.GlobalContentTypeRules)
+ // .Select(ct => ct.ContentType)
+ // .Distinct()
+ // .OrderBy(n => n)
+ // .ToList();
+
+ //var contentTypeCodeGenList = settings
+ // .XmlGenerationLambdas
+ // .Select(xgl => xgl.Key)
+ // .OrderBy(n => n)
+ // .ToList();
+
+ //var rulesWithoutGenCode = contentTypeList
+ // .Except(contentTypeCodeGenList)
+ // .ToList();
+
+ //var codeGenWithoutRules = contentTypeCodeGenList
+ // .Except(contentTypeList)
+ // .ToList();
+
+ //var s10 = codeGenWithoutRules.Select(m => m + Environment.NewLine).StringConcatenate();
+ //Console.WriteLine(s10);
+
+ //var s9 = contentTypeList.Select(m => m + Environment.NewLine).StringConcatenate();
+ //Console.WriteLine(s9);
+
+ // Apply the Document rules first, then apply the DocumentType rules, then apply the Global rules. First one that matches, wins.
+ foreach (var rule in settings.DocumentContentTypeRules.Concat(settings.DocumentTypeContentTypeRules).Concat(settings.GlobalContentTypeRules))
+ {
+ bool stylePass = false;
+ bool styleRegexPass = false;
+ bool regexPass = false;
+ bool matchLambdaPass = false;
+
+ stylePass = rule.StyleName == null || rule.StyleName.ToUpper() == styleOfBlcUC;
+
+ if (stylePass)
+ {
+ styleRegexPass = rule.StyleNameRegex == null;
+ if (rule.StyleNameRegex != null && styleOfBlc != null)
+ styleRegexPass = rule.StyleNameRegex.IsMatch(styleOfBlc);
+ }
+
+ if (stylePass && styleRegexPass)
+ {
+ regexPass = rule.RegexArray == null;
+ if (rule.RegexArray != null)
+ {
+ for (int i = 0; i < rule.RegexArray.Length; i++)
+ {
+ // clone the blc because OpenXmlRegex.Match replaces content, mucks with the run, probably should not if it only is used to find content.
+ var clonedBlc = new XElement(blc);
+
+ // following removes the subtitle created by a soft break, so that the pattern matches appropriately.
+ clonedBlc = RemoveContentAfterBR(clonedBlc);
+
+#if false
+<p p1:FontName="Georgia" p1:LanguageType="western" p1:AbstractNumId="28" xmlns:p1="http://powertools.codeplex.com/2011" xmlns="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
+ <r p1:ListItemRun="1.1" p1:FontName="Georgia" p1:LanguageType="western">
+ <t xml:space="preserve">1.1</t>
+ </r>
+#endif
+ // remove list item runs so that they are not matched in the content
+ clonedBlc.Elements(W.r).Where(r => r.Attribute(PtOpenXml.ListItemRun) != null).Remove();
+
+ if (OpenXmlRegex.Match(new[] { clonedBlc }, rule.RegexArray[i]) != 0)
+ {
+ regexPass = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (stylePass && styleRegexPass && regexPass)
+ {
+ matchLambdaPass = rule.MatchLambda == null;
+ if (rule.MatchLambda != null)
+ {
+ if (rule.MatchLambda(blc, rule, wDoc, settings))
+ matchLambdaPass = true;
+ }
+ }
+
+ if (stylePass && styleRegexPass && regexPass && matchLambdaPass)
+ {
+ AddContentTypeToBlockContent(settings, part, blc, rule.ContentType);
+ if (rule.ApplyRunContentTypes)
+ ApplyRunContentTypes(settings, ctai, wDoc, blc, settings.RunContentTypeRules, part, partXDoc);
+ break;
+ }
+ }
+ }
+
+ if (settings.ProgressFunction != null)
+ {
+ WmlToXmlProgressInfo pi = new WmlToXmlProgressInfo()
+ {
+ ContentTotal = totalCount,
+ ContentCount = totalCount,
+ InProgressMessage = Environment.NewLine + " Done" + Environment.NewLine,
+ };
+ settings.ProgressFunction(pi);
+ }
+
+ part.PutXDocument();
+ var mainPart = part as MainDocumentPart;
+ if (mainPart != null)
+ {
+ if (mainPart.WordprocessingCommentsPart != null)
+ mainPart.WordprocessingCommentsPart.PutXDocument();
+ }
+ }
+
+ private static XElement RemoveContentAfterBR(XElement clonedBlc)
+ {
+ if (clonedBlc.Name != W.p)
+ return clonedBlc;
+ var cloned2 = new XElement(clonedBlc.Name,
+ clonedBlc.Attributes(),
+ clonedBlc.Elements().TakeWhile(r => r.Element(W.br) == null));
+ return cloned2;
+ }
+
+ private static void ApplyRunContentTypes(WmlToXmlSettings settings, ContentTypeApplierInfo ctai, WordprocessingDocument wDoc,
+ XElement blockLevelContent, List<ContentTypeRule> runContentTypeRuleList, OpenXmlPart part, XDocument mainXDoc)
+ {
+ var runContent = blockLevelContent.Descendants()
+ .Where(d => d.Name == W.r || d.Name == W.hyperlink || d.Name == W.sdt || d.Name == W.bookmarkStart);
+ foreach (var rlc in runContent)
+ {
+ if (rlc.Name == W.r || rlc.Name == W.sdt)
+ {
+ var runStyle = (string)rlc.Elements(W.rPr).Elements(W.rStyle).Attributes(W.val).FirstOrDefault();
+ if (runStyle == null)
+ runStyle = ctai.DefaultCharacterStyleName;
+ foreach (var rule in runContentTypeRuleList)
+ {
+ if (rule.StyleName != null && rule.StyleName != runStyle)
+ continue;
+
+ if (rule.RegexArray != null)
+ throw new OpenXmlPowerToolsException("Invalid Run ContentType Rule - Regex not allowed");
+ if (rule.MatchLambda != null)
+ {
+ if (rule.MatchLambda(rlc, rule, wDoc, settings))
+ {
+ AddContentTypeToRunContent(settings, part, rlc, rule.ContentType);
+ break;
+ }
+ continue;
+ }
+ AddContentTypeToRunContent(settings, part, rlc, rule.ContentType);
+ break;
+ }
+ }
+ else if (rlc.Name == W.hyperlink)
+ {
+ foreach (var run in rlc.Descendants(W.r))
+ AddContentTypeToRunContent(settings, part, run, "Hyperlink");
+ }
+ else if (rlc.Name == W.bookmarkStart)
+ {
+ AddContentTypeToRunContent(settings, part, rlc, "Anchor");
+ }
+ }
+ }
+
+ private static XAttribute[] NamespaceAttributes =
+ {
+ new XAttribute(XNamespace.Xmlns + "wpc", WPC.wpc),
+ new XAttribute(XNamespace.Xmlns + "mc", MC.mc),
+ new XAttribute(XNamespace.Xmlns + "o", O.o),
+ new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XAttribute(XNamespace.Xmlns + "m", M.m),
+ new XAttribute(XNamespace.Xmlns + "v", VML.vml),
+ new XAttribute(XNamespace.Xmlns + "wp14", WP14.wp14),
+ new XAttribute(XNamespace.Xmlns + "wp", WP.wp),
+ new XAttribute(XNamespace.Xmlns + "w10", W10.w10),
+ new XAttribute(XNamespace.Xmlns + "w", W.w),
+ new XAttribute(XNamespace.Xmlns + "w14", W14.w14),
+ new XAttribute(XNamespace.Xmlns + "w15", W15.w15),
+ new XAttribute(XNamespace.Xmlns + "w16se", W16SE.w16se),
+ new XAttribute(XNamespace.Xmlns + "wpg", WPG.wpg),
+ new XAttribute(XNamespace.Xmlns + "wpi", WPI.wpi),
+ new XAttribute(XNamespace.Xmlns + "wne", WNE.wne),
+ new XAttribute(XNamespace.Xmlns + "wps", WPS.wps),
+ new XAttribute(XNamespace.Xmlns + "pt", PtOpenXml.pt),
+ new XAttribute(MC.Ignorable, "w14 wp14 w15 w16se pt"),
+ };
+
+ private static void AddContentTypeToBlockContent(WmlToXmlSettings settings, OpenXmlPart part, XElement blc, string contentType)
+ {
+ // add the attribute to the block content
+ blc.Add(new XAttribute(PtOpenXml.ContentType, contentType));
+
+ var mainPart = part as MainDocumentPart;
+ if (mainPart != null)
+ {
+ // add a comment, if appropriate
+ int commentNumber = 1;
+ XDocument newComments = null;
+ if (settings.InjectCommentForContentTypes != null && (bool)settings.InjectCommentForContentTypes)
+ {
+ if (mainPart.WordprocessingCommentsPart != null)
+ {
+ newComments = mainPart.WordprocessingCommentsPart.GetXDocument();
+ newComments.Declaration.Standalone = "yes";
+ newComments.Declaration.Encoding = "UTF-8";
+ var ids = newComments.Root.Elements(W.comment).Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ commentNumber = ids.Max() + 1;
+ }
+ else
+ {
+ part.AddNewPart<WordprocessingCommentsPart>();
+ newComments = mainPart.WordprocessingCommentsPart.GetXDocument();
+ newComments.Declaration.Standalone = "yes";
+ newComments.Declaration.Encoding = "UTF-8";
+ newComments.Add(new XElement(W.comments, NamespaceAttributes));
+ commentNumber = 1;
+ }
+#if false
+ <w:comment w:id="12"
+ w:author="Eric White"
+ w:date="2016-03-20T18:50:00Z"
+ w:initials="EW">
+ <w:p w14:paraId="7E227B98"
+ w14:textId="6FA2BE6B"
+ w:rsidR="00425889"
+ w:rsidRDefault="00425889">
+ <w:pPr>
+ <w:pStyle w:val="CommentText"/>
+ </w:pPr>
+ <w:r>
+ <w:rPr>
+ <w:rStyle w:val="CommentReference"/>
+ </w:rPr>
+ <w:annotationRef/>
+ </w:r>
+ <w:r>
+ <w:t>Nil</w:t>
+ </w:r>
+ </w:p>
+ </w:comment>
+#endif
+ XElement newElement = new XElement(W.comment,
+ new XAttribute(W.id, commentNumber),
+ new XElement(W.p,
+ new XElement(W.pPr,
+ new XElement(W.pStyle,
+ new XAttribute(W.val, "CommentText"))),
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "CommentReference"))),
+ new XElement(W.annotationRef)),
+ new XElement(W.r,
+ new XElement(W.t,
+ new XText(contentType)))));
+ newComments.Root.Add(newElement);
+
+#if false
+ <w:r>
+ <w:rPr>
+ <w:rStyle w:val="CommentReference"/>
+ </w:rPr>
+ <w:commentReference w:id="12"/>
+ </w:r>
+#endif
+
+ XElement commentRun = new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle, new XAttribute(W.val, "CommentReference"))),
+ new XElement(W.commentReference,
+ new XAttribute(W.id, commentNumber)));
+ var firstRunInParagraph = blc
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(r => r.Name == W.r)
+ .FirstOrDefault();
+ if (firstRunInParagraph != null)
+ {
+ // for now, only do the work of inserting a comment if it is easy. For content types for tables, rows and cells, not inserting a comment.
+ if (firstRunInParagraph.Parent.Name == W.p)
+ firstRunInParagraph.AddBeforeSelf(commentRun);
+ }
+ else
+ {
+ // for now, only do the work of inserting a comment if it is easy. For content types for tables, rows and cells, not inserting a comment.
+ if (blc.Name == W.p)
+ blc.Add(commentRun);
+ }
+
+ if (mainPart.StyleDefinitionsPart == null)
+ {
+ throw new ContentApplierException("Document does not have styles definition part");
+ }
+ XDocument stylesXDoc = mainPart.StyleDefinitionsPart.GetXDocument();
+
+ var style =
+@"<w:style w:type=""paragraph""
+ w:styleId=""CommentText""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""annotation text""/>
+ <w:basedOn w:val=""Normal""/>
+ <w:link w:val=""CommentTextChar""/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:sz w:val=""20""/>
+ <w:szCs w:val=""20""/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ style =
+@"<w:style w:type=""paragraph""
+ w:styleId=""CommentSubject""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""annotation subject""/>
+ <w:basedOn w:val=""CommentText""/>
+ <w:next w:val=""CommentText""/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:b/>
+ <w:bCs/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ style =
+@"<w:style w:type=""character""
+ w:styleId=""CommentReference""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""annotation reference""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:rsid w:val=""00872729""/>
+ <w:rPr>
+ <w:sz w:val=""16""/>
+ <w:szCs w:val=""16""/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ style =
+@"<w:style w:type=""character""
+ w:customStyle=""1""
+ w:styleId=""CommentTextChar""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""Comment Text Char""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:link w:val=""CommentText""/>
+ <w:semiHidden/>
+ <w:rsid w:val=""00A43CEC""/>
+ <w:rPr>
+ <w:lang w:val=""en-GB""
+ w:eastAsia=""zh-CN""/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ mainPart.StyleDefinitionsPart.PutXDocument();
+ }
+ }
+
+ var root = blc.Ancestors().LastOrDefault();
+ if (root == null)
+ throw new ContentApplierException("Internal error");
+ var ptNamespace = root.Attribute(XNamespace.Xmlns + "pt");
+ if (ptNamespace == null)
+ {
+ root.Add(new XAttribute(XNamespace.Xmlns + "pt", PtOpenXml.pt.NamespaceName));
+ }
+ var ignorable = (string)root.Attribute(MC.Ignorable);
+ if (ignorable != null)
+ {
+ var list = ignorable.Split(' ');
+ if (!list.Contains("pt"))
+ {
+ ignorable += " pt";
+ root.Attribute(MC.Ignorable).Value = ignorable;
+ }
+ }
+ else
+ {
+ root.Add(new XAttribute(MC.Ignorable, "pt"));
+ }
+ }
+
+ private static void AddContentTypeToRunContent(WmlToXmlSettings settings, OpenXmlPart part, XElement rlc, string contentType)
+ {
+ // if there is already a content type for this run level content, then nothing to do. First one wins.
+ if (rlc.Attribute(PtOpenXml.ContentType) != null)
+ return;
+
+ // add the attribute to the block level content
+ rlc.Add(new XAttribute(PtOpenXml.ContentType, contentType));
+
+ var mainPart = part as MainDocumentPart;
+ if (mainPart != null)
+ {
+ // add a comment, if appropriate
+ int commentNumber = 1;
+ XDocument newComments = null;
+ if (settings.InjectCommentForContentTypes != null && (bool)settings.InjectCommentForContentTypes)
+ {
+ if (mainPart.WordprocessingCommentsPart != null)
+ {
+ newComments = mainPart.WordprocessingCommentsPart.GetXDocument();
+ newComments.Declaration.Standalone = "yes";
+ newComments.Declaration.Encoding = "UTF-8";
+ var ids = newComments.Root.Elements(W.comment).Select(f => (int)f.Attribute(W.id));
+ if (ids.Any())
+ commentNumber = ids.Max() + 1;
+ }
+ else
+ {
+ mainPart.AddNewPart<WordprocessingCommentsPart>();
+ newComments = mainPart.WordprocessingCommentsPart.GetXDocument();
+ newComments.Declaration.Standalone = "yes";
+ newComments.Declaration.Encoding = "UTF-8";
+ newComments.Add(new XElement(W.comments, NamespaceAttributes));
+ commentNumber = 1;
+ }
+ XElement newElement = new XElement(W.comment,
+ new XAttribute(W.id, commentNumber),
+ new XElement(W.p,
+ new XElement(W.pPr,
+ new XElement(W.pStyle,
+ new XAttribute(W.val, "CommentText"))),
+ new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle,
+ new XAttribute(W.val, "CommentReference"))),
+ new XElement(W.annotationRef)),
+ new XElement(W.r,
+ new XElement(W.t,
+ new XText(contentType)))));
+ newComments.Root.Add(newElement);
+ XElement commentRun = new XElement(W.r,
+ new XElement(W.rPr,
+ new XElement(W.rStyle, new XAttribute(W.val, "CommentReference"))),
+ new XElement(W.commentReference,
+ new XAttribute(W.id, commentNumber)));
+ var firstRunInParagraph = rlc
+ .DescendantsTrimmed(W.txbxContent)
+ .Where(r => r.Name == W.r)
+ .FirstOrDefault();
+
+ // for now, only do the work of inserting a comment if it is easy. For content types for tables, rows and cells, not inserting a comment.
+ if (rlc.Parent.Name == W.p)
+ rlc.AddBeforeSelf(commentRun);
+ if (mainPart.StyleDefinitionsPart == null)
+ {
+ throw new ContentApplierException("Document does not have styles definition part");
+ }
+ XDocument stylesXDoc = mainPart.StyleDefinitionsPart.GetXDocument();
+
+ var style =
+@"<w:style w:type=""paragraph""
+ w:styleId=""CommentText""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""annotation text""/>
+ <w:basedOn w:val=""Normal""/>
+ <w:link w:val=""CommentTextChar""/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:sz w:val=""20""/>
+ <w:szCs w:val=""20""/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ style =
+@"<w:style w:type=""paragraph""
+ w:styleId=""CommentSubject""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""annotation subject""/>
+ <w:basedOn w:val=""CommentText""/>
+ <w:next w:val=""CommentText""/>
+ <w:semiHidden/>
+ <w:rPr>
+ <w:b/>
+ <w:bCs/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ style =
+@"<w:style w:type=""character""
+ w:styleId=""CommentReference""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""annotation reference""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:uiPriority w:val=""99""/>
+ <w:semiHidden/>
+ <w:unhideWhenUsed/>
+ <w:rsid w:val=""00872729""/>
+ <w:rPr>
+ <w:sz w:val=""16""/>
+ <w:szCs w:val=""16""/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ style =
+@"<w:style w:type=""character""
+ w:customStyle=""1""
+ w:styleId=""CommentTextChar""
+ xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
+ <w:name w:val=""Comment Text Char""/>
+ <w:basedOn w:val=""DefaultParagraphFont""/>
+ <w:link w:val=""CommentText""/>
+ <w:semiHidden/>
+ <w:rsid w:val=""00A43CEC""/>
+ <w:rPr>
+ <w:lang w:val=""en-GB""
+ w:eastAsia=""zh-CN""/>
+ </w:rPr>
+ </w:style>
+";
+ AddIfMissing(stylesXDoc, style);
+ mainPart.StyleDefinitionsPart.PutXDocument();
+ }
+ }
+
+ var root = rlc.Ancestors().LastOrDefault();
+ if (root == null)
+ throw new ContentApplierException("Internal error");
+ var ptNamespace = root.Attribute(XNamespace.Xmlns + "pt");
+ if (ptNamespace == null)
+ {
+ root.Add(new XAttribute(XNamespace.Xmlns + "pt", PtOpenXml.pt.NamespaceName));
+ }
+ var ignorable = (string)root.Attribute(MC.Ignorable);
+ if (ignorable != null)
+ {
+ var list = ignorable.Split(' ');
+ if (!list.Contains("pt"))
+ {
+ ignorable += " pt";
+ root.Attribute(MC.Ignorable).Value = ignorable;
+ }
+ }
+ else
+ {
+ root.Add(new XAttribute(MC.Ignorable, "pt"));
+ }
+ }
+
+ private static void AddIfMissing(XDocument stylesXDoc, string commentStyle)
+ {
+ XElement e1 = XElement.Parse(commentStyle);
+#if false
+ <w:style w:type=""character""
+ w:customStyle=""1""
+ w:styleId=""CommentTextChar""
+#endif
+ var existingStyle = stylesXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(e2 =>
+ {
+ XName name = W.type;
+ string v1 = (string)e1.Attribute(name);
+ string v2 = (string)e2.Attribute(name);
+ if (v1 != v2)
+ return false;
+ name = W.customStyle;
+ v1 = (string)e1.Attribute(name);
+ v2 = (string)e2.Attribute(name);
+ if (v1 != v2)
+ return false;
+ name = W.styleId;
+ v1 = (string)e1.Attribute(name);
+ v2 = (string)e2.Attribute(name);
+ if (v1 != v2)
+ return false;
+ return true;
+ });
+ if (existingStyle != null)
+ return;
+ stylesXDoc.Root.Add(e1);
+ }
+
+ private static void AssembleListItemInformation(WordprocessingDocument wordDoc, ListItemRetrieverSettings settings)
+ {
+ XDocument xDoc = wordDoc.MainDocumentPart.GetXDocument();
+ foreach (var para in xDoc.Descendants(W.p))
+ {
+ ListItemRetriever.RetrieveListItem(wordDoc, para, settings);
+ }
+ }
+
+ private class ContentTypeApplierInfo
+ {
+ public string DefaultParagraphStyleName;
+ public string DefaultCharacterStyleName;
+ public string DefaultTableStyleName;
+ public ContentTypeApplierInfo()
+ {
+ }
+ }
+
+ public class ContentApplierException : Exception
+ {
+ public ContentApplierException(string message) : base(message) { }
+ }
+
+ public static List<WmlToXmlValidationError> ValidateContentTypeXml(WmlDocument wmlRawSourceDocument, WmlDocument wmlWithContentTypeApplied, XElement contentTypeXml, WmlToXmlSettings settings)
+ {
+ List<WmlToXmlValidationError> errorList = new List<WmlToXmlValidationError>();
+
+ using (MemoryStream msContentTypeApplied = new MemoryStream())
+ using (MemoryStream msRawSourceDocument = new MemoryStream())
+ {
+ msContentTypeApplied.Write(wmlWithContentTypeApplied.DocumentByteArray, 0, wmlWithContentTypeApplied.DocumentByteArray.Length);
+ msRawSourceDocument.Write(wmlRawSourceDocument.DocumentByteArray, 0, wmlRawSourceDocument.DocumentByteArray.Length);
+ using (WordprocessingDocument wDocContentTypeApplied = WordprocessingDocument.Open(msContentTypeApplied, true))
+ using (WordprocessingDocument wDocRawSourceDocument = WordprocessingDocument.Open(msRawSourceDocument, true))
+ {
+ foreach (var vr in settings.GlobalValidationRules)
+ {
+ if (vr.GlobalRuleLambda != null)
+ {
+ var valErrors = vr.GlobalRuleLambda(vr, wDocRawSourceDocument, wDocContentTypeApplied, contentTypeXml, settings);
+ if (valErrors != null && valErrors.Any())
+ {
+ foreach (var ve in valErrors)
+ {
+ errorList.Add(ve);
+ }
+ }
+ }
+ }
+ var mXDoc = wDocContentTypeApplied.MainDocumentPart.GetXDocument();
+ var sXDoc = wDocContentTypeApplied.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
+
+ var defaultParagraphStyle = sXDoc
+ .Root
+ .Elements(W.style)
+ .FirstOrDefault(s => (string)s.Attribute(W._default) == "1");
+
+ string defaultParagraphStyleName = null;
+ if (defaultParagraphStyle != null)
+ defaultParagraphStyleName = (string)defaultParagraphStyle.Attribute(W.styleId);
+
+ foreach (var blc in mXDoc.Root.Descendants().Where(d => d.Name == W.p || d.Name == W.tbl || d.Name == W.tr || d.Name == W.tc))
+ {
+ var styleId = (string)blc
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault();
+ var styleName = (string)sXDoc
+ .Root
+ .Elements(W.style)
+ .Where(s => (string)s.Attribute(W.styleId) == styleId)
+ .Elements(W.name)
+ .Attributes(W.val)
+ .FirstOrDefault();
+
+ if (styleName == null && blc.Name == W.p)
+ styleName = defaultParagraphStyleName;
+
+ foreach (var vr in settings.BlockLevelContentValidationRules)
+ {
+ bool matchStyle = true;
+ if (vr.StyleNameRegex != null)
+ {
+ if (styleName == null)
+ {
+ matchStyle = false;
+ }
+ else
+ {
+ var match = vr.StyleNameRegex.Match(styleName);
+ matchStyle = match.Success;
+ }
+ }
+ if (matchStyle && vr.BlockLevelContentRuleLambda != null)
+ {
+ var valErrors = vr.BlockLevelContentRuleLambda(blc, vr, wDocContentTypeApplied, contentTypeXml, settings);
+ if (valErrors != null && valErrors.Any())
+ {
+ foreach (var ve in valErrors)
+ {
+ errorList.Add(ve);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return errorList;
+ }
+ }
+
+ public static class WmlToXmlUtil
+ {
+ public static WmlDocument AssignUnidToBlc(WmlDocument document)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ ms.Write(document.DocumentByteArray, 0, document.DocumentByteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
+ {
+ var xDoc = wDoc.MainDocumentPart.GetXDocument();
+ List<XElement> elementsInOrder = new List<XElement>();
+ DetermineElementOrder(xDoc.Root.Descendants(W.body).FirstOrDefault(), elementsInOrder);
+ var unid = 1;
+ foreach (var b in elementsInOrder)
+ {
+ var unidString = unid.ToString();
+ if (b.Attribute(PtOpenXml.Unid) != null)
+ b.Attribute(PtOpenXml.Unid).Value = unidString;
+ else
+ b.Add(new XAttribute(PtOpenXml.Unid, unidString));
+ unid++;
+ }
+ IgnorePt14Namespace(xDoc.Root);
+ wDoc.MainDocumentPart.PutXDocument();
+ }
+ var result = new WmlDocument(document.FileName, ms.ToArray());
+ return result;
+ }
+ }
+
+ private static void DetermineElementOrder(XElement element, List<XElement> elementList)
+ {
+ foreach (var childElement in element.Elements())
+ {
+ if (childElement.Name == W.p)
+ {
+ elementList.Add(childElement);
+ continue;
+ }
+ else if (childElement.Name == W.tbl || childElement.Name == W.tc || childElement.Name == W.sdt ||
+ childElement.Name == W.sdtContent)
+ {
+ DetermineElementOrder(childElement, elementList);
+ continue;
+ }
+ else if (childElement.Name == W.tr)
+ {
+ foreach (var tc in childElement.Elements())
+ DetermineElementOrder(tc, elementList);
+ elementList.Add(childElement);
+ continue;
+ }
+ }
+ }
+
+ private static void IgnorePt14Namespace(XElement root)
+ {
+ if (root.Attribute(XNamespace.Xmlns + "pt14") == null)
+ {
+ root.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName));
+ }
+ var ignorable = (string)root.Attribute(MC.Ignorable);
+ if (ignorable != null)
+ {
+ var list = ignorable.Split(' ');
+ if (!list.Contains("pt14"))
+ {
+ ignorable += " pt14";
+ root.Attribute(MC.Ignorable).Value = ignorable;
+ }
+ }
+ else
+ {
+ root.Add(new XAttribute(MC.Ignorable, "pt14"));
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/WorksheetAccessor.cs b/OpenXmlPowerTools/WorksheetAccessor.cs
new file mode 100644
index 0000000..0ca9f6f
--- /dev/null
+++ b/OpenXmlPowerTools/WorksheetAccessor.cs
@@ -0,0 +1,2200 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Text;
+using System.Collections.Generic;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.Xml;
+using ExcelFormula;
+
+namespace OpenXmlPowerTools
+{
+ // Classes for "bulk load" of a spreadsheet
+ public class MemorySpreadsheet
+ {
+ private SortedList<int, MemoryRow> rowList;
+
+ public MemorySpreadsheet()
+ {
+ rowList = new SortedList<int, MemoryRow>();
+ }
+
+ public void SetCellValue(int row, int column, object value)
+ {
+ if (!rowList.ContainsKey(row))
+ rowList.Add(row, new MemoryRow(row));
+ MemoryRow mr = rowList[row];
+ mr.SetCell(new MemoryCell(column, value));
+ }
+
+ public void SetCellValue(int row, int column, object value, int styleIndex)
+ {
+ if (!rowList.ContainsKey(row))
+ rowList.Add(row, new MemoryRow(row));
+ MemoryRow mr = rowList[row];
+ mr.SetCell(new MemoryCell(column, value, styleIndex));
+ }
+
+ public object GetCellValue(int row, int column)
+ {
+ if (!rowList.ContainsKey(row))
+ return null;
+ MemoryCell cell = rowList[row].GetCell(column);
+ if (cell == null)
+ return null;
+ return cell.GetValue();
+ }
+
+ public XElement GetElements()
+ {
+ XElement root = new XElement(S.sheetData);
+ foreach (KeyValuePair<int, MemoryRow> item in rowList)
+ root.Add(item.Value.GetElements());
+ return root;
+ }
+ }
+
+ public class MemoryRow
+ {
+ private int row;
+ private SortedList<int, MemoryCell> cellList;
+
+ public MemoryRow(int Row)
+ {
+ row = Row;
+ cellList = new SortedList<int, MemoryCell>();
+ }
+
+ public MemoryCell GetCell(int column)
+ {
+ if (!cellList.ContainsKey(column))
+ return null;
+ return cellList[column];
+ }
+
+ public void SetCell(MemoryCell cell)
+ {
+ if (cellList.ContainsKey(cell.GetColumn()))
+ cellList.Remove(cell.GetColumn());
+ cellList.Add(cell.GetColumn(), cell);
+ }
+
+ public XElement GetElements()
+ {
+ XElement root = new XElement(S.row, new XAttribute(NoNamespace.r, row));
+ foreach (KeyValuePair<int, MemoryCell> item in cellList)
+ root.Add(item.Value.GetElements(row));
+ return root;
+ }
+ }
+
+ public class MemoryCell
+ {
+ private int column;
+ private object cellValue;
+ private int styleIndex;
+
+ public MemoryCell(int col, object value)
+ {
+ column = col;
+ cellValue = value;
+ }
+
+ public MemoryCell(int col, object value, int style)
+ {
+ column = col;
+ cellValue = value;
+ styleIndex = style;
+ }
+
+ public int GetColumn()
+ {
+ return column;
+ }
+ public object GetValue()
+ {
+ return cellValue;
+ }
+ public int GetStyleIndex()
+ {
+ return styleIndex;
+ }
+
+ public XElement GetElements(int row)
+ {
+ string cellReference = WorksheetAccessor.GetColumnId(column) + row.ToString();
+ XElement newCell = null;
+
+ if (cellValue is int || cellValue is double)
+ newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XElement(S.v, cellValue.ToString()));
+ else if (cellValue is bool)
+ newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "b"), new XElement(S.v, (bool)cellValue ? "1" : "0"));
+ else if (cellValue is string)
+ {
+ newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "inlineStr"),
+ new XElement(S._is, new XElement(S.t, cellValue.ToString())));
+ }
+ if (newCell == null)
+ throw new ArgumentException("Invalid cell type.");
+ if (styleIndex != 0)
+ newCell.Add(new XAttribute(NoNamespace.s, styleIndex));
+
+ return newCell;
+ }
+ }
+
+ // Static methods to modify worksheets in SpreadsheetML
+ public class WorksheetAccessor
+ {
+ private static XNamespace ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ private static XNamespace relationshipsns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
+
+ // Finds the WorksheetPart by sheet name
+ public static WorksheetPart GetWorksheet(SpreadsheetDocument document, string worksheetName)
+ {
+ XDocument workbook = document.WorkbookPart.GetXDocument();
+ return (WorksheetPart)document.WorkbookPart.GetPartById(
+ workbook.Root.Element(S.sheets).Elements(S.sheet).Where(
+ s => s.Attribute(NoNamespace.name).Value.ToLower().Equals(worksheetName.ToLower()))
+ .FirstOrDefault().Attribute(R.id).Value);
+ }
+
+ // Creates a new worksheet with the specified name
+ public static WorksheetPart AddWorksheet(SpreadsheetDocument document, string worksheetName)
+ {
+ // Create the empty sheet
+ WorksheetPart worksheetPart = document.WorkbookPart.AddNewPart<WorksheetPart>();
+ worksheetPart.PutXDocument(new XDocument(
+ new XElement(S.worksheet, new XAttribute("xmlns", S.s), new XAttribute(XNamespace.Xmlns + "r", R.r),
+ new XElement(S.sheetData))));
+ XDocument wb = document.WorkbookPart.GetXDocument();
+
+ // Generate a unique sheet ID number
+ int sheetId = 1;
+ if (wb.Root.Element(S.sheets).Elements(S.sheet).Count() != 0)
+ sheetId = wb.Root.Element(S.sheets).Elements(S.sheet).Max(n => Convert.ToInt32(n.Attribute(NoNamespace.sheetId).Value)) + 1;
+
+ // If name is null, generate a name based on the sheet ID
+ if (worksheetName == null)
+ worksheetName = "Sheet" + sheetId.ToString();
+
+ // Create the new sheet element in the workbook
+ wb.Root.Element(S.sheets).Add(new XElement(S.sheet,
+ new XAttribute(NoNamespace.name, worksheetName),
+ new XAttribute(NoNamespace.sheetId, sheetId),
+ new XAttribute(R.id, document.WorkbookPart.GetIdOfPart(worksheetPart))));
+ document.WorkbookPart.PutXDocument();
+ return worksheetPart;
+ }
+
+ // Creates a new worksheet with the specified name and contents from a memory spreadsheet
+ public static void SetSheetContents(SpreadsheetDocument document, WorksheetPart worksheet, MemorySpreadsheet contents)
+ {
+ XDocument worksheetXDocument = worksheet.GetXDocument();
+ worksheetXDocument.Root.Element(S.sheetData).ReplaceWith(contents.GetElements());
+ worksheet.PutXDocument();
+ }
+
+ // Translates the column number to the column reference string (e.g. 1 -> A, 2-> B)
+ public static string GetColumnId(int columnNumber)
+ {
+ string result = "";
+ do
+ {
+ result = ((char)((columnNumber - 1) % 26 + (int)'A')).ToString() + result;
+ columnNumber = (columnNumber - 1) / 26;
+ } while (columnNumber != 0);
+ return result;
+ }
+
+ // Gets the value of the specified cell
+ // Returned object can be double/Double, int/Int32, bool/Boolean or string/String types
+ public static object GetCellValue(SpreadsheetDocument document, WorksheetPart worksheet, int column, int row)
+ {
+ XDocument worksheetXDocument = worksheet.GetXDocument();
+ XElement cellValue = GetCell(worksheetXDocument, column, row);
+
+ if (cellValue != null)
+ {
+ if (cellValue.Attribute(NoNamespace.t) == null)
+ {
+ string value = cellValue.Element(S.v).Value;
+ if (value.Contains("."))
+ return Convert.ToDouble(value);
+ return Convert.ToInt32(value);
+ }
+ switch (cellValue.Attribute(NoNamespace.t).Value)
+ {
+ case "b":
+ return (cellValue.Element(S.v).Value == "1");
+ case "s":
+ return GetSharedString(document, System.Convert.ToInt32(cellValue.Element(S.v).Value));
+ case "inlineStr":
+ return cellValue.Element(S._is).Element(S.t).Value;
+ }
+ }
+ return null;
+ }
+
+ // Finds the shared string using its index
+ private static string GetSharedString(SpreadsheetDocument document, int index)
+ {
+ XDocument sharedStringsXDocument = document.WorkbookPart.SharedStringTablePart.GetXDocument();
+ return sharedStringsXDocument.Root.Elements().ElementAt<XElement>(index).Value;
+ }
+
+ // Gets the cell element (c) for the specified cell
+ private static XElement GetCell(XDocument worksheet, int column, int row)
+ {
+ string cellReference = GetColumnId(column) + row.ToString();
+ XElement rowElement = worksheet.Root
+ .Element(S.sheetData)
+ .Elements(S.row)
+ .Where(r => r.Attribute(NoNamespace.r).Value.Equals(row.ToString())).FirstOrDefault<XElement>();
+ if (rowElement == null)
+ return null;
+ return rowElement.Elements(S.c).Where(c => c.Attribute(NoNamespace.r).Value.Equals(cellReference)).FirstOrDefault<XElement>();
+ }
+
+ // Sets the value for the specified cell
+ // The "value" must be double/Double, int/Int32, bool/Boolean or string/String type
+ public static void SetCellValue(SpreadsheetDocument document, WorksheetPart worksheet, int row, int column, object value)
+ {
+ XDocument worksheetXDocument = worksheet.GetXDocument();
+ string cellReference = GetColumnId(column) + row.ToString();
+ XElement newCell = null;
+
+ if (value is int || value is double)
+ newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XElement(S.v, value.ToString()));
+ else if (value is bool)
+ newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "b"), new XElement(S.v, (bool)value ? "1" : "0"));
+ else if (value is string)
+ {
+ newCell = new XElement(S.c, new XAttribute(NoNamespace.r, cellReference), new XAttribute(NoNamespace.t, "inlineStr"),
+ new XElement(S._is, new XElement(S.t, value.ToString())));
+ }
+ if (newCell == null)
+ throw new ArgumentException("Invalid cell type.");
+
+ SetCell(worksheetXDocument, newCell);
+ }
+
+ // Sets the specified cell
+ private static void SetCell(XDocument worksheetXDocument, XElement newCell)
+ {
+ int row;
+ int column;
+ string cellReference = newCell.Attribute(NoNamespace.r).Value;
+ GetRowColumn(cellReference, out row, out column);
+
+ // Find the row containing the cell to add the value to
+ XElement rowElement = worksheetXDocument.Root
+ .Element(S.sheetData)
+ .Elements(S.row)
+ .Where(t => t.Attribute(NoNamespace.r).Value == row.ToString())
+ .FirstOrDefault();
+
+ if (rowElement == null)
+ {
+ //row element does not exist
+ //create a new one
+ rowElement = CreateEmptyRow(row);
+
+ //row elements must appear in order inside sheetData element
+ if (worksheetXDocument.Root.Element(S.sheetData).HasElements)
+ { //if there are more rows already defined at sheetData element
+ //find the row with the inmediate higher index for the row containing the cell to set the value to
+ XElement rowAfterElement = FindRowAfter(worksheetXDocument, row);
+ //if there is a row with an inmediate higher index already defined at sheetData
+ if (rowAfterElement != null)
+ {
+ //add the new row before the row with an inmediate higher index
+ rowAfterElement.AddBeforeSelf(rowElement);
+ }
+ else
+ { //this row is going to be the one with the highest index (add it as the last element for sheetData)
+ worksheetXDocument.Root.Element(S.sheetData).Elements(S.row).Last().AddAfterSelf(rowElement);
+ }
+ }
+ else
+ { //there are no other rows already defined at sheetData
+ //Add a new row elemento to sheetData
+ worksheetXDocument.Root.Element(S.sheetData).Add(rowElement);
+ }
+
+ //Add the new cell to the row Element
+ rowElement.Add(newCell);
+ }
+ else
+ {
+ //row containing the cell to set the value to is already defined at sheetData
+ //look if cell already exist at that row
+ XElement currentCell = rowElement
+ .Elements(S.c)
+ .Where(t => t.Attribute(NoNamespace.r).Value == cellReference)
+ .FirstOrDefault();
+
+ if (currentCell == null)
+ { //cell element does not exist at row indicated as parameter
+ //find the inmediate right column for the cell to set the value to
+ XElement columnAfterXElement = FindColumAfter(worksheetXDocument, row, column);
+ if (columnAfterXElement != null)
+ {
+ //Insert the new cell before the inmediate right column
+ columnAfterXElement.AddBeforeSelf(newCell);
+ }
+ else
+ { //There is no inmediate right cell
+ //Add the new cell as the last element for the row
+ rowElement.Add(newCell);
+ }
+ }
+ else
+ {
+ //cell alreay exist
+ //replace the current cell with that with the new value
+ currentCell.ReplaceWith(newCell);
+ }
+ }
+ }
+
+ // Finds the row element (r) with a higher number than the specified "row" number
+ private static XElement FindRowAfter(XDocument worksheet, int row)
+ {
+ return worksheet.Root
+ .Element(S.sheetData)
+ .Elements(S.row)
+ .FirstOrDefault(r => System.Convert.ToInt32(r.Attribute(NoNamespace.r).Value) > row);
+ }
+
+ // Finds the cell element (c) in the specified row that is after the specified "column" number
+ private static XElement FindColumAfter(XDocument worksheet, int row, int column)
+ {
+ return worksheet.Root
+ .Element(S.sheetData)
+ .Elements(S.row)
+ .FirstOrDefault(r => System.Convert.ToInt32(r.Attribute(NoNamespace.r).Value) == row)
+ .Elements(S.c)
+ .FirstOrDefault(c => GetColumnNumber(c.Attribute(NoNamespace.r).Value) > GetColumnNumber(GetColumnId(column) + row));
+ }
+
+ // Converts the column reference string to a column number (e.g. A -> 1, B -> 2)
+ public static int GetColumnNumber(string cellReference)
+ {
+ int columnNumber = 0;
+ foreach (char c in cellReference)
+ {
+ if (Char.IsLetter(c))
+ columnNumber = columnNumber * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
+ }
+ return columnNumber;
+ }
+
+ // Converts a cell reference string into the row and column numbers for that cell
+ // e.g. G5 -> [row = 5, column = 7]
+ private static void GetRowColumn(string cellReference, out int row, out int column)
+ {
+ row = 0;
+ column = 0;
+ foreach (char c in cellReference)
+ {
+ if (Char.IsLetter(c))
+ column = column * 26 + System.Convert.ToInt32(c) - System.Convert.ToInt32('A') + 1;
+ else
+ row = row * 10 + System.Convert.ToInt32(c) - System.Convert.ToInt32('0');
+ }
+ }
+
+ // Returns the row and column numbers and worksheet part for the named range
+ public static WorksheetPart GetRange(SpreadsheetDocument doc, string rangeName, out int startRow, out int startColumn, out int endRow, out int endColumn)
+ {
+ XDocument book = doc.WorkbookPart.GetXDocument();
+ if (book.Root.Element(S.definedNames) == null)
+ throw new ArgumentException("Range name not found: " + rangeName);
+ XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
+ .Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
+ if (element == null)
+ throw new ArgumentException("Range name not found: " + rangeName);
+ string sheetName = element.Value.Substring(0, element.Value.IndexOf('!'));
+ string range = element.Value.Substring(element.Value.IndexOf('!') + 1).Replace("$","");
+ int colonIndex = range.IndexOf(':');
+ GetRowColumn(range.Substring(0, colonIndex), out startRow, out startColumn);
+ GetRowColumn(range.Substring(colonIndex + 1), out endRow, out endColumn);
+ return GetWorksheet(doc, sheetName);
+ }
+
+ // Sets the named range with the specified range of row and column numbers
+ public static void SetRange(SpreadsheetDocument doc, string rangeName, string sheetName, int startRow, int startColumn, int endRow, int endColumn)
+ {
+ XDocument book = doc.WorkbookPart.GetXDocument();
+ if (book.Root.Element(S.definedNames) == null)
+ book.Root.Add(new XElement(S.definedNames));
+ XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
+ .Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
+ if (element == null)
+ {
+ element = new XElement(S.definedName, new XAttribute(NoNamespace.name, rangeName));
+ book.Root.Element(S.definedNames).Add(element);
+ }
+ element.SetValue(String.Format("{0}!${1}${2}:${3}${4}", sheetName, GetColumnId(startColumn), startRow, GetColumnId(endColumn), endRow));
+ doc.WorkbookPart.PutXDocument();
+ }
+
+ // Sets the end row for the named range
+ public static void UpdateRangeEndRow(SpreadsheetDocument doc, string rangeName, int lastRow)
+ {
+ // Update named range used by pivot table
+ XDocument book = doc.WorkbookPart.GetXDocument();
+ XElement element = book.Root.Element(S.definedNames).Elements(S.definedName)
+ .Where(t => t.Attribute(NoNamespace.name).Value == rangeName).FirstOrDefault();
+ if (element != null)
+ {
+ string original = element.Value;
+ element.SetValue(original.Substring(0, original.Length - 1) + lastRow.ToString());
+ }
+ doc.WorkbookPart.PutXDocument();
+ }
+
+ // Creates an empty row element (r) with the specified row number
+ private static XElement CreateEmptyRow(int row)
+ {
+ return new XElement(S.row, new XAttribute(NoNamespace.r, row.ToString()));
+ }
+
+ public static void ForceCalculateOnLoad(SpreadsheetDocument document)
+ {
+ XDocument book = document.WorkbookPart.GetXDocument();
+ XElement element = book.Root.Element(S.calcPr);
+ if (element == null)
+ {
+ book.Root.Add(new XElement(S.calcPr));
+ }
+ element.SetAttributeValue(NoNamespace.fullCalcOnLoad, "1");
+ document.WorkbookPart.PutXDocument();
+ }
+
+ public static void FormulaReplaceSheetName(SpreadsheetDocument document, string oldName, string newName)
+ {
+ foreach (WorksheetPart sheetPart in document.WorkbookPart.WorksheetParts)
+ {
+ XDocument sheetDoc = sheetPart.GetXDocument();
+ bool changed = false;
+ foreach (XElement formula in sheetDoc.Descendants(S.f))
+ {
+ ParseFormula parser = new ParseFormula(formula.Value);
+ string newFormula = parser.ReplaceSheetName(oldName, newName);
+ if (newFormula != formula.Value)
+ {
+ formula.SetValue(newFormula);
+ changed = true;
+ }
+ }
+ if (changed)
+ {
+ sheetPart.PutXDocument();
+ ForceCalculateOnLoad(document);
+ }
+ }
+ }
+
+ // Copy all cells in the specified range to a new location
+ public static void CopyCellRange(SpreadsheetDocument document, WorksheetPart worksheet, int startRow, int startColumn, int endRow, int endColumn,
+ int toRow, int toColumn)
+ {
+ int rowOffset = toRow - startRow;
+ int columnOffset = toColumn - startColumn;
+ XDocument worksheetXDocument = worksheet.GetXDocument();
+ for (int row = startRow; row <= endRow; row++)
+ for (int column = startColumn; column <= endColumn; column++)
+ {
+ XElement oldCell = GetCell(worksheetXDocument, column, row);
+ if (oldCell != null)
+ {
+ XElement newCell = new XElement(oldCell);
+ newCell.SetAttributeValue(NoNamespace.r, GetColumnId(column + columnOffset) + (row + rowOffset).ToString());
+ XElement formula = newCell.Element(S.f);
+ if (formula != null)
+ {
+ ParseFormula parser = new ParseFormula(formula.Value);
+ formula.SetValue(parser.ReplaceRelativeCell(rowOffset, columnOffset));
+ }
+ SetCell(worksheetXDocument, newCell);
+ }
+ }
+ worksheet.PutXDocument();
+ ForceCalculateOnLoad(document);
+ }
+
+ // Creates a pivot table in the specified sheet using the specified range name
+ // The new pivot table will not be configured with any fields in the rows, columns, filters or values
+ public static PivotTablePart CreatePivotTable(SpreadsheetDocument document, string rangeName, WorksheetPart sheet)
+ {
+ int startRow, startColumn, endRow, endColumn;
+ WorksheetPart sourceSheet = GetRange(document, rangeName, out startRow, out startColumn, out endRow, out endColumn);
+
+ // Fill out pivotFields element (for PivotTablePart) and cacheFields element (for PivotTableCacheDefinitionPart)
+ // with an element for each column in the source range
+ XElement pivotFields = new XElement(S.pivotFields, new XAttribute(NoNamespace.count, (endColumn - startColumn + 1).ToString()));
+ XElement cacheFields = new XElement(S.cacheFields, new XAttribute(NoNamespace.count, (endColumn - startColumn + 1).ToString()));
+ for (int column = startColumn; column <= endColumn; column++)
+ {
+ pivotFields.Add(new XElement(S.pivotField, new XAttribute(NoNamespace.showAll, "0")));
+ XElement sharedItems = new XElement(S.sharedItems);
+ // Determine numeric sharedItems values, if any
+ object value = GetCellValue(document, sourceSheet, column, startRow + 1);
+ if (value is double || value is Int32)
+ {
+ bool hasDouble = false;
+ double minValue = Convert.ToDouble(value);
+ double maxValue = Convert.ToDouble(value);
+ if (value is double)
+ hasDouble = true;
+ for (int row = startRow + 1; row <= endRow; row++)
+ {
+ value = GetCellValue(document, sourceSheet, column, row);
+ if (value is double)
+ hasDouble = true;
+ if (Convert.ToDouble(value) < minValue)
+ minValue = Convert.ToDouble(value);
+ if (Convert.ToDouble(value) > maxValue)
+ maxValue = Convert.ToDouble(value);
+ }
+ sharedItems.Add(new XAttribute(NoNamespace.containsSemiMixedTypes, "0"),
+ new XAttribute(NoNamespace.containsString, "0"), new XAttribute(NoNamespace.containsNumber, "1"),
+ new XAttribute(NoNamespace.minValue, minValue.ToString()), new XAttribute(NoNamespace.maxValue, maxValue.ToString()));
+ if (!hasDouble)
+ sharedItems.Add(new XAttribute(NoNamespace.containsInteger, "1"));
+ }
+ cacheFields.Add(new XElement(S.cacheField, new XAttribute(NoNamespace.name, GetCellValue(document, sourceSheet, column, startRow).ToString()),
+ new XAttribute(NoNamespace.numFmtId, "0"), sharedItems));
+ }
+
+ // Fill out pivotCacheRecords element (for PivotTableCacheRecordsPart) with an element
+ // for each row in the source range
+ XElement pivotCacheRecords = new XElement(S.pivotCacheRecords, new XAttribute("xmlns", S.s),
+ new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(NoNamespace.count, (endRow - startRow).ToString()));
+ for (int row = startRow + 1; row <= endRow; row++)
+ {
+ XElement r = new XElement(S.r);
+
+ // Fill the record element with a value from each column in the source row
+ for (int column = startColumn; column <= endColumn; column++)
+ {
+ object value = GetCellValue(document, sourceSheet, column, row);
+ if (value is String)
+ r.Add(new XElement(S._s, new XAttribute(NoNamespace.v, value.ToString())));
+ else
+ r.Add(new XElement(S.n, new XAttribute(NoNamespace.v, value.ToString())));
+ }
+ pivotCacheRecords.Add(r);
+ }
+
+ // Create pivot table parts with proper links
+ PivotTablePart pivotTable = sheet.AddNewPart<PivotTablePart>();
+ PivotTableCacheDefinitionPart cacheDef = pivotTable.AddNewPart<PivotTableCacheDefinitionPart>();
+ PivotTableCacheRecordsPart records = cacheDef.AddNewPart<PivotTableCacheRecordsPart>();
+ document.WorkbookPart.AddPart<PivotTableCacheDefinitionPart>(cacheDef);
+
+ // Set content for the PivotTableCacheRecordsPart and PivotTableCacheDefinitionPart
+ records.PutXDocument(new XDocument(pivotCacheRecords));
+ cacheDef.PutXDocument(new XDocument(new XElement(S.pivotCacheDefinition, new XAttribute("xmlns", S.s),
+ new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(R.id, cacheDef.GetIdOfPart(records)),
+ new XAttribute(NoNamespace.recordCount, (endRow - startRow).ToString()),
+ new XElement(S.cacheSource, new XAttribute(NoNamespace.type, "worksheet"),
+ new XElement(S.worksheetSource, new XAttribute(NoNamespace.name, rangeName))),
+ cacheFields)));
+
+ // Create the pivotCache entry in the workbook part
+ int cacheId = 1;
+ XDocument wb = document.WorkbookPart.GetXDocument();
+ if (wb.Root.Element(S.pivotCaches) == null)
+ wb.Root.Add(new XElement(S.pivotCaches));
+ else
+ {
+ if (wb.Root.Element(S.pivotCaches).Elements(S.pivotCache).Count() != 0)
+ cacheId = wb.Root.Element(S.pivotCaches).Elements(S.pivotCache).Max(n => Convert.ToInt32(n.Attribute(NoNamespace.cacheId).Value)) + 1;
+ }
+ wb.Root.Element(S.pivotCaches).Add(new XElement(S.pivotCache,
+ new XAttribute(NoNamespace.cacheId, cacheId),
+ new XAttribute(R.id, document.WorkbookPart.GetIdOfPart(cacheDef))));
+ document.WorkbookPart.PutXDocument();
+
+ // Set the content for the PivotTablePart
+ pivotTable.PutXDocument(new XDocument(new XElement(S.pivotTableDefinition, new XAttribute("xmlns", S.s),
+ new XAttribute(NoNamespace.name, "PivotTable1"), new XAttribute(NoNamespace.cacheId, cacheId.ToString()),
+ new XAttribute(NoNamespace.dataCaption, "Values"),
+ new XElement(S.location, new XAttribute(NoNamespace._ref, "A3:C20"),
+ new XAttribute(NoNamespace.firstHeaderRow, "1"), new XAttribute(NoNamespace.firstDataRow, "1"),
+ new XAttribute(NoNamespace.firstDataCol, "0")), pivotFields)));
+
+ return pivotTable;
+ }
+
+ public enum PivotAxis { Row, Column, Page };
+ public static void AddPivotAxis(SpreadsheetDocument document, WorksheetPart sheet, string fieldName, PivotAxis axis)
+ {
+ // Create indexed items in cache and definition
+ PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
+ PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
+ PivotTableCacheRecordsPart recordsPart = cacheDefPart.GetPartsOfType<PivotTableCacheRecordsPart>().First();
+ XDocument cacheDef = cacheDefPart.GetXDocument();
+ int index = Array.FindIndex(cacheDef.Descendants(S.cacheField).ToArray(),
+ z => z.Attribute(NoNamespace.name).Value == fieldName);
+ XDocument records = recordsPart.GetXDocument();
+ List<XElement> values = new List<XElement>();
+ foreach (XElement rec in records.Descendants(S.r))
+ {
+ XElement val = rec.Elements().Skip(index).First();
+ int x = Array.FindIndex(values.ToArray(), z => XElement.DeepEquals(z, val));
+ if (x == -1)
+ {
+ values.Add(val);
+ x = values.Count() - 1;
+ }
+ val.ReplaceWith(new XElement(S.x, new XAttribute(NoNamespace.v, x)));
+ }
+ XElement sharedItems = cacheDef.Descendants(S.cacheField).Skip(index).First().Element(S.sharedItems);
+ sharedItems.Add(new XAttribute(NoNamespace.count, values.Count()), values);
+ recordsPart.PutXDocument();
+ cacheDefPart.PutXDocument();
+
+ // Add axis definition to pivot table field
+ XDocument pivotTable = pivotTablePart.GetXDocument();
+ XElement pivotField = pivotTable.Descendants(S.pivotField).Skip(index).First();
+ XElement items = new XElement(S.items, new XAttribute(NoNamespace.count, values.Count() + 1),
+ values.OrderBy(z => z.Attribute(NoNamespace.v).Value).Select(z => new XElement(S.item,
+ new XAttribute(NoNamespace.x, Array.FindIndex(values.ToArray(),
+ a => a.Attribute(NoNamespace.v).Value == z.Attribute(NoNamespace.v).Value)))));
+ items.Add(new XElement(S.item, new XAttribute(NoNamespace.t, "default")));
+ switch (axis)
+ {
+ case PivotAxis.Column:
+ pivotField.Add(new XAttribute(NoNamespace.axis, "axisCol"), items);
+ // Add to colFields
+ {
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.colFields, new XAttribute(NoNamespace.count, 0));
+ XElement rowFields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (rowFields == null)
+ pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
+ else
+ rowFields.AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, index)));
+ fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
+ }
+ break;
+ case PivotAxis.Row:
+ pivotField.Add(new XAttribute(NoNamespace.axis, "axisRow"), items);
+ // Add to rowFields
+ {
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.rowFields, new XAttribute(NoNamespace.count, 0));
+ pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, index)));
+ fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
+ }
+ break;
+ case PivotAxis.Page:
+ pivotField.Add(new XAttribute(NoNamespace.axis, "axisPage"), items);
+ // Add to pageFields
+ {
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.pageFields, new XAttribute(NoNamespace.count, 0));
+ XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
+ if (prev == null)
+ prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (prev == null)
+ pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
+ else
+ prev.AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.pageField, new XAttribute(NoNamespace.fld, index)));
+ fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
+ }
+ break;
+ }
+ pivotTablePart.PutXDocument();
+ ForcePivotRefresh(cacheDefPart);
+ }
+
+ public static void AddDataValueLabel(SpreadsheetDocument document, WorksheetPart sheet, PivotAxis axis)
+ {
+ PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
+ XDocument pivotTable = pivotTablePart.GetXDocument();
+ switch (axis)
+ {
+ case PivotAxis.Column:
+ // Add to colFields
+ {
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.colFields, new XAttribute(NoNamespace.count, 0));
+ XElement rowFields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (rowFields == null)
+ pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
+ else
+ rowFields.AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, -2)));
+ fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
+ }
+ break;
+ case PivotAxis.Row:
+ // Add to rowFields
+ {
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.rowFields, new XAttribute(NoNamespace.count, 0));
+ pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.field, new XAttribute(NoNamespace.x, -2)));
+ fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
+ }
+ break;
+ case PivotAxis.Page:
+ // Add to pageFields
+ {
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.pageFields, new XAttribute(NoNamespace.count, 0));
+ XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
+ if (prev == null)
+ prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (prev == null)
+ pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields).AddAfterSelf(fields);
+ else
+ prev.AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.pageField, new XAttribute(NoNamespace.fld, -2)));
+ fields.Attribute(NoNamespace.count).Value = fields.Elements(S.field).Count().ToString();
+ }
+ break;
+ }
+ pivotTablePart.PutXDocument();
+ PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
+ ForcePivotRefresh(cacheDefPart);
+ }
+
+ public static void AddDataValue(SpreadsheetDocument document, WorksheetPart sheet, string fieldName)
+ {
+ PivotTablePart pivotTablePart = sheet.GetPartsOfType<PivotTablePart>().First();
+ PivotTableCacheDefinitionPart cacheDefPart = pivotTablePart.GetPartsOfType<PivotTableCacheDefinitionPart>().First();
+ XDocument cacheDef = cacheDefPart.GetXDocument();
+ int index = Array.FindIndex(cacheDef.Descendants(S.cacheField).ToArray(),
+ z => z.Attribute(NoNamespace.name).Value == fieldName);
+ XDocument pivotTable = pivotTablePart.GetXDocument();
+ XElement pivotField = pivotTable.Descendants(S.pivotField).Skip(index).First();
+ pivotField.Add(new XAttribute(NoNamespace.dataField, "1"));
+ XElement fields = pivotTable.Element(S.pivotTableDefinition).Element(S.dataFields);
+ if (fields == null)
+ {
+ fields = new XElement(S.dataFields, new XAttribute(NoNamespace.count, 0));
+ XElement prev = pivotTable.Element(S.pivotTableDefinition).Element(S.pageFields);
+ if (prev == null)
+ prev = pivotTable.Element(S.pivotTableDefinition).Element(S.colFields);
+ if (prev == null)
+ prev = pivotTable.Element(S.pivotTableDefinition).Element(S.rowFields);
+ if (prev == null)
+ prev = pivotTable.Element(S.pivotTableDefinition).Element(S.pivotFields);
+ prev.AddAfterSelf(fields);
+ }
+ fields.Add(new XElement(S.dataField, new XAttribute(NoNamespace.name, "Sum of " + fieldName),
+ new XAttribute(NoNamespace.fld, index), new XAttribute(NoNamespace.baseField, 0),
+ new XAttribute(NoNamespace.baseItem, 0)));
+ int count = fields.Elements(S.dataField).Count();
+ fields.Attribute(NoNamespace.count).Value = count.ToString();
+ if (count == 2)
+ { // Only when data field count goes from 1 to 2 do we add a special column to label the data fields
+ AddDataValueLabel(document, sheet, PivotAxis.Column);
+ }
+ pivotTablePart.PutXDocument();
+ ForcePivotRefresh(cacheDefPart);
+ }
+
+ private static void ForcePivotRefresh(PivotTableCacheDefinitionPart cacheDef)
+ {
+ XDocument doc = cacheDef.GetXDocument();
+ XElement def = doc.Element(S.pivotCacheDefinition);
+ if (def.Attribute(NoNamespace.refreshOnLoad) == null)
+ def.Add(new XAttribute(NoNamespace.refreshOnLoad, 1));
+ else
+ def.Attribute(NoNamespace.refreshOnLoad).Value = "1";
+ cacheDef.PutXDocument();
+ }
+
+ public static void CheckNumberFormat(SpreadsheetDocument document, int fmtID, string formatCode)
+ {
+ XElement numFmt = new XElement(S.numFmt, new XAttribute(NoNamespace.numFmtId, fmtID.ToString()),
+ new XAttribute(NoNamespace.formatCode, formatCode));
+ XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ XElement numFmts = styles.Root.Element(S.numFmts);
+ if (numFmts == null)
+ {
+ styles.Root.Element(S.fonts).AddBeforeSelf(new XElement(S.numFmts, new XAttribute(NoNamespace.count, "0")));
+ numFmts = styles.Root.Element(S.numFmts);
+ }
+ int index = Array.FindIndex(numFmts.Elements(S.numFmt).ToArray(),
+ z => XElement.DeepEquals(z, numFmt));
+ if (index == -1)
+ {
+ numFmts.Add(numFmt);
+ numFmts.Attribute(NoNamespace.count).Value = numFmts.Elements(S.numFmt).Count().ToString();
+ document.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ }
+ }
+
+ public class ColorInfo
+ {
+ public enum ColorType { Theme, Indexed };
+
+ private bool Auto;
+ private string RGB;
+ private int Indexed;
+ private int Theme;
+ private double Tint;
+
+ public ColorInfo()
+ {
+ Auto = true;
+ }
+ public ColorInfo(ColorType type, int value)
+ {
+ if (type == ColorType.Indexed)
+ Indexed = value;
+ else if (type == ColorType.Theme)
+ Theme = value;
+ }
+ public ColorInfo(int theme, double tint)
+ {
+ Theme = theme;
+ Tint = tint;
+ }
+ public ColorInfo(string rgb)
+ {
+ RGB = rgb;
+ }
+
+ public XElement GetXElement(XName colorName)
+ {
+ XElement color = new XElement(colorName);
+ if (Auto)
+ color.Add(new XAttribute(NoNamespace.auto, "1"));
+ else if (RGB != null)
+ color.Add(new XAttribute(NoNamespace.rgb, RGB));
+ else if (Indexed != 0)
+ color.Add(new XAttribute(NoNamespace.indexed, Indexed));
+ else
+ color.Add(new XAttribute(NoNamespace.theme, Theme));
+ if (Tint != 0)
+ color.Add(new XAttribute(NoNamespace.tint, Tint));
+ return color;
+ }
+ }
+
+ public class Font
+ {
+ public enum SchemeType { None, Major, Minor };
+
+ public bool Bold { get; set; }
+ public ColorInfo Color { get; set; }
+ public bool Condense { get; set; }
+ public bool Extend { get; set; }
+ public int Family { get; set; }
+ public bool Italic { get; set; }
+ public string Name { get; set; }
+ public bool Outline { get; set; }
+ public SchemeType Scheme { get; set; }
+ public bool Shadow { get; set; }
+ public bool StrikeThrough { get; set; }
+ public int Size { get; set; }
+ public bool Underline { get; set; }
+
+ public XElement GetXElement()
+ {
+ XElement font = new XElement(S.font);
+ if (Bold)
+ font.Add(new XElement(S.b));
+ if (Italic)
+ font.Add(new XElement(S.i));
+ if (Underline)
+ font.Add(new XElement(S.u));
+ if (StrikeThrough)
+ font.Add(new XElement(S.strike));
+ if (Condense)
+ font.Add(new XElement(S.condense));
+ if (Extend)
+ font.Add(new XElement(S.extend));
+ if (Outline)
+ font.Add(new XElement(S.outline));
+ if (Shadow)
+ font.Add(new XElement(S.shadow));
+ if (Size != 0)
+ font.Add(new XElement(S.sz, new XAttribute(NoNamespace.val, Size.ToString())));
+ if (Color != null)
+ font.Add(Color.GetXElement(S.color));
+ if (Name != null)
+ font.Add(new XElement(S.name, new XAttribute(NoNamespace.val, Name)));
+ if (Family != 0)
+ font.Add(new XElement(S.family, new XAttribute(NoNamespace.val, Family.ToString())));
+ switch (Scheme)
+ {
+ case SchemeType.Major:
+ font.Add(new XElement(S.scheme, new XAttribute(NoNamespace.val, "major")));
+ break;
+ case SchemeType.Minor:
+ font.Add(new XElement(S.scheme, new XAttribute(NoNamespace.val, "minor")));
+ break;
+ }
+ return font;
+ }
+ }
+
+ public static int GetFontIndex(SpreadsheetDocument document, Font f)
+ {
+ XElement font = f.GetXElement();
+ XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ XElement fonts = styles.Root.Element(S.fonts);
+ int index = Array.FindIndex(fonts.Elements(S.font).ToArray(),
+ z => XElement.DeepEquals(z, font));
+ if (index != -1)
+ return index;
+ fonts.Add(font);
+ fonts.Attribute(NoNamespace.count).Value = fonts.Elements(S.font).Count().ToString();
+ document.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ return fonts.Elements(S.font).Count() - 1;
+ }
+
+ public class PatternFill
+ {
+ public enum PatternType
+ {
+ None, Solid, DarkDown, DarkGray, DarkGrid, DarkHorizontal, DarkTrellis, DarkUp, DarkVertical,
+ Gray0625, Gray125, LightDown, LightGray, LightGrid, LightHorizontal, LightTrellis, LightUp, LightVertical, MediumGray
+ };
+
+ private PatternType Pattern;
+ private ColorInfo BgColor;
+ private ColorInfo FgColor;
+
+ public PatternFill(PatternType pattern, ColorInfo bgColor, ColorInfo fgColor)
+ {
+ Pattern = pattern;
+ BgColor = bgColor;
+ FgColor = fgColor;
+ }
+
+ public XElement GetXElement()
+ {
+ XElement pattern = new XElement(S.patternFill);
+ switch (Pattern)
+ {
+ case PatternType.DarkDown:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkDown"));
+ break;
+ case PatternType.DarkGray:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkGray"));
+ break;
+ case PatternType.DarkGrid:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkGrid"));
+ break;
+ case PatternType.DarkHorizontal:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkHorizontal"));
+ break;
+ case PatternType.DarkTrellis:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkTrellis"));
+ break;
+ case PatternType.DarkUp:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkUp"));
+ break;
+ case PatternType.DarkVertical:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "darkVertical"));
+ break;
+ case PatternType.Gray0625:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "gray0625"));
+ break;
+ case PatternType.Gray125:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "gray125"));
+ break;
+ case PatternType.LightDown:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightDown"));
+ break;
+ case PatternType.LightGray:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightGray"));
+ break;
+ case PatternType.LightGrid:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightGrid"));
+ break;
+ case PatternType.LightHorizontal:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightHorizontal"));
+ break;
+ case PatternType.LightTrellis:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightTrellis"));
+ break;
+ case PatternType.LightUp:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightUp"));
+ break;
+ case PatternType.LightVertical:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "lightVertical"));
+ break;
+ case PatternType.MediumGray:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "mediumGray"));
+ break;
+ case PatternType.None:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "none"));
+ break;
+ case PatternType.Solid:
+ pattern.Add(new XAttribute(NoNamespace.patternType, "solid"));
+ break;
+ }
+ if (FgColor != null)
+ pattern.Add(FgColor.GetXElement(S.fgColor));
+ if (BgColor != null)
+ pattern.Add(BgColor.GetXElement(S.bgColor));
+ return new XElement(S.fill, pattern);
+ }
+ }
+
+ public class GradientStop
+ {
+ private double Position;
+ private ColorInfo Color;
+
+ public GradientStop(double position, ColorInfo color)
+ {
+ Position = position;
+ Color = color;
+ }
+
+ public XElement GetXElement()
+ {
+ return new XElement(S.stop, new XAttribute(NoNamespace.position, Position), Color.GetXElement(S.color));
+ }
+ }
+
+ public class GradientFill
+ {
+ private bool PathGradient;
+ private int LinearDegree;
+ private double PathTop;
+ private double PathLeft;
+ private double PathBottom;
+ private double PathRight;
+ private List<GradientStop> Stops;
+
+ public GradientFill(int degree)
+ {
+ PathGradient = false;
+ LinearDegree = degree;
+ Stops = new List<GradientStop>();
+ }
+
+ public GradientFill(double top, double left, double bottom, double right)
+ {
+ PathGradient = true;
+ PathTop = top;
+ PathLeft = left;
+ PathBottom = bottom;
+ PathRight = right;
+ Stops = new List<GradientStop>();
+ }
+
+ public void AddStop(GradientStop stop)
+ {
+ Stops.Add(stop);
+ }
+
+ public XElement GetXElement()
+ {
+ XElement gradient = new XElement(S.gradientFill);
+ if (PathGradient)
+ {
+ gradient.Add(new XAttribute(NoNamespace.type, "path"),
+ new XAttribute(NoNamespace.left, PathLeft.ToString()), new XAttribute(NoNamespace.right, PathRight.ToString()),
+ new XAttribute(NoNamespace.top, PathTop.ToString()), new XAttribute(NoNamespace.bottom, PathBottom.ToString()));
+ }
+ else
+ {
+ gradient.Add(new XAttribute(NoNamespace.degree, LinearDegree.ToString()));
+ }
+ foreach (GradientStop stop in Stops)
+ gradient.Add(stop.GetXElement());
+ return new XElement(S.fill, gradient);
+ }
+ }
+
+ public static int GetFillIndex(SpreadsheetDocument document, PatternFill patternFill)
+ {
+ return GetFillIndex(document, patternFill.GetXElement());
+ }
+
+ public static int GetFillIndex(SpreadsheetDocument document, GradientFill gradientFill)
+ {
+ return GetFillIndex(document, gradientFill.GetXElement());
+ }
+
+ private static int GetFillIndex(SpreadsheetDocument document, XElement fill)
+ {
+ XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ XElement fills = styles.Root.Element(S.fills);
+ int index = Array.FindIndex(fills.Elements(S.fill).ToArray(),
+ z => XElement.DeepEquals(z, fill));
+ if (index != -1)
+ return index;
+ fills.Add(fill);
+ fills.Attribute(NoNamespace.count).Value = fills.Elements(S.fill).Count().ToString();
+ document.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ return fills.Elements(S.fill).Count() - 1;
+ }
+
+ public class BorderLine
+ {
+ public enum LineStyle
+ {
+ None, DashDot, DashDotDot, Dashed, Dotted, Double, Hair,
+ Medium, MediumDashDot, MediumDashDotDot, MediumDashed, SlantDashDot, Thick, Thin
+ };
+ private LineStyle Style;
+ private ColorInfo Color;
+
+ public BorderLine(LineStyle style, ColorInfo color)
+ {
+ Style = style;
+ Color = color;
+ }
+
+ public XElement GetXElement(XName name)
+ {
+ XElement line = new XElement(name);
+ switch (Style)
+ {
+ case LineStyle.DashDot:
+ line.Add(new XAttribute(NoNamespace.style, "dashDot"));
+ break;
+ case LineStyle.DashDotDot:
+ line.Add(new XAttribute(NoNamespace.style, "dashDotDot"));
+ break;
+ case LineStyle.Dashed:
+ line.Add(new XAttribute(NoNamespace.style, "dashed"));
+ break;
+ case LineStyle.Dotted:
+ line.Add(new XAttribute(NoNamespace.style, "dotted"));
+ break;
+ case LineStyle.Double:
+ line.Add(new XAttribute(NoNamespace.style, "double"));
+ break;
+ case LineStyle.Hair:
+ line.Add(new XAttribute(NoNamespace.style, "hair"));
+ break;
+ case LineStyle.Medium:
+ line.Add(new XAttribute(NoNamespace.style, "medium"));
+ break;
+ case LineStyle.MediumDashDot:
+ line.Add(new XAttribute(NoNamespace.style, "mediumDashDot"));
+ break;
+ case LineStyle.MediumDashDotDot:
+ line.Add(new XAttribute(NoNamespace.style, "mediumDashDotDot"));
+ break;
+ case LineStyle.MediumDashed:
+ line.Add(new XAttribute(NoNamespace.style, "mediumDashed"));
+ break;
+ case LineStyle.SlantDashDot:
+ line.Add(new XAttribute(NoNamespace.style, "slantDashDot"));
+ break;
+ case LineStyle.Thick:
+ line.Add(new XAttribute(NoNamespace.style, "thick"));
+ break;
+ case LineStyle.Thin:
+ line.Add(new XAttribute(NoNamespace.style, "thin"));
+ break;
+ }
+ line.Add(Color.GetXElement(S.color));
+ return line;
+ }
+ }
+
+ public class Border
+ {
+ public BorderLine Top { get; set; }
+ public BorderLine Bottom { get; set; }
+ public BorderLine Left { get; set; }
+ public BorderLine Right { get; set; }
+ public BorderLine Horizontal { get; set; }
+ public BorderLine Vertical { get; set; }
+ public BorderLine Diagonal { get; set; }
+ public bool DiagonalDown { get; set; }
+ public bool DiagonalUp { get; set; }
+ public bool Outline { get; set; }
+
+ public XElement GetXElement()
+ {
+ XElement border = new XElement(S.border);
+ if (DiagonalDown)
+ border.Add(new XAttribute(NoNamespace.diagonalDown, "1"));
+ if (DiagonalUp)
+ border.Add(new XAttribute(NoNamespace.diagonalUp, "1"));
+ if (Outline)
+ border.Add(new XAttribute(NoNamespace.outline, "1"));
+ if (Left == null)
+ border.Add(new XElement(S.left));
+ else
+ border.Add(Left.GetXElement(S.left));
+ if (Right == null)
+ border.Add(new XElement(S.right));
+ else
+ border.Add(Right.GetXElement(S.right));
+ if (Top == null)
+ border.Add(new XElement(S.top));
+ else
+ border.Add(Top.GetXElement(S.top));
+ if (Bottom == null)
+ border.Add(new XElement(S.bottom));
+ else
+ border.Add(Bottom.GetXElement(S.bottom));
+ if (Diagonal == null)
+ border.Add(new XElement(S.diagonal));
+ else
+ border.Add(Diagonal.GetXElement(S.diagonal));
+ if (Horizontal != null)
+ border.Add(Horizontal.GetXElement(S.horizontal));
+ if (Vertical != null)
+ border.Add(Vertical.GetXElement(S.vertical));
+ return border;
+ }
+ }
+
+ public static int GetBorderIndex(SpreadsheetDocument document, Border b)
+ {
+ XElement border = b.GetXElement();
+ XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ XElement borders = styles.Root.Element(S.borders);
+ int index = Array.FindIndex(borders.Elements(S.border).ToArray(),
+ z => XElement.DeepEquals(z, border));
+ if (index != -1)
+ return index;
+ borders.Add(border);
+ borders.Attribute(NoNamespace.count).Value = borders.Elements(S.border).Count().ToString();
+ document.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ return borders.Elements(S.border).Count() - 1;
+ }
+
+ public static int GetStyleIndex(SpreadsheetDocument document, string styleName)
+ {
+ XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ string xfId = styles.Root.Element(S.cellStyles).Elements(S.cellStyle)
+ .Where(t => t.Attribute(NoNamespace.name).Value == styleName)
+ .FirstOrDefault().Attribute(NoNamespace.xfId).Value;
+ XElement cellXfs = styles.Root.Element(S.cellXfs);
+ int index = Array.FindIndex(cellXfs.Elements(S.xf).ToArray(),
+ z => z.Attribute(NoNamespace.xfId).Value == xfId);
+ if (index != -1)
+ return index;
+ XElement cellStyleXf = styles.Root.Element(S.cellStyleXfs).Elements(S.xf).ToArray()[Convert.ToInt32(xfId)];
+ if (cellStyleXf != null)
+ { // Create new xf element under cellXfs
+ cellXfs.Add(new XElement(S.xf, new XAttribute(NoNamespace.numFmtId, cellStyleXf.Attribute(NoNamespace.numFmtId).Value),
+ new XAttribute(NoNamespace.fontId, cellStyleXf.Attribute(NoNamespace.fontId).Value),
+ new XAttribute(NoNamespace.fillId, cellStyleXf.Attribute(NoNamespace.fillId).Value),
+ new XAttribute(NoNamespace.borderId, cellStyleXf.Attribute(NoNamespace.borderId).Value),
+ new XAttribute(NoNamespace.xfId, xfId)));
+ cellXfs.Attribute(NoNamespace.count).Value = cellXfs.Elements(S.xf).Count().ToString();
+ document.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ return cellXfs.Elements(S.xf).Count() - 1;
+ }
+
+ return 0;
+ }
+
+ public class CellAlignment
+ {
+ public enum Horizontal { General, Center, CenterContinuous, Distributed, Fill, Justify, Left, Right };
+ public enum Vertical { Bottom, Center, Distributed, Justify, Top };
+
+ public Horizontal HorizontalAlignment { get; set; }
+ public int Indent { get; set; }
+ public bool JustifyLastLine { get; set; }
+ public int ReadingOrder { get; set; }
+ public bool ShrinkToFit { get; set; }
+ public int TextRotation { get; set; }
+ public Vertical VerticalAlignment { get; set; }
+ public bool WrapText { get; set; }
+
+ public CellAlignment()
+ {
+ HorizontalAlignment = Horizontal.General;
+ Indent = 0;
+ JustifyLastLine = false;
+ ReadingOrder = 0;
+ ShrinkToFit = false;
+ TextRotation = 0;
+ VerticalAlignment = Vertical.Bottom;
+ WrapText = false;
+ }
+
+ public XElement GetXElement()
+ {
+ XElement align = new XElement(S.alignment);
+ switch (HorizontalAlignment)
+ {
+ case Horizontal.Center:
+ align.Add(new XAttribute(NoNamespace.horizontal, "center"));
+ break;
+ case Horizontal.CenterContinuous:
+ align.Add(new XAttribute(NoNamespace.horizontal, "centerContinuous"));
+ break;
+ case Horizontal.Distributed:
+ align.Add(new XAttribute(NoNamespace.horizontal, "distributed"));
+ break;
+ case Horizontal.Fill:
+ align.Add(new XAttribute(NoNamespace.horizontal, "fill"));
+ break;
+ case Horizontal.Justify:
+ align.Add(new XAttribute(NoNamespace.horizontal, "justify"));
+ break;
+ case Horizontal.Left:
+ align.Add(new XAttribute(NoNamespace.horizontal, "left"));
+ break;
+ case Horizontal.Right:
+ align.Add(new XAttribute(NoNamespace.horizontal, "right"));
+ break;
+ }
+ if (Indent != 0)
+ align.Add(new XAttribute(NoNamespace.indent, Indent));
+ if (JustifyLastLine)
+ align.Add(new XAttribute(NoNamespace.justifyLastLine, true));
+ if (ReadingOrder != 0)
+ align.Add(new XAttribute(NoNamespace.readingOrder, ReadingOrder));
+ if (ShrinkToFit)
+ align.Add(new XAttribute(NoNamespace.shrinkToFit, true));
+ if (TextRotation != 0)
+ align.Add(new XAttribute(NoNamespace.textRotation, TextRotation));
+ switch (VerticalAlignment)
+ {
+ case Vertical.Center:
+ align.Add(new XAttribute(NoNamespace.vertical, "center"));
+ break;
+ case Vertical.Distributed:
+ align.Add(new XAttribute(NoNamespace.vertical, "distributed"));
+ break;
+ case Vertical.Justify:
+ align.Add(new XAttribute(NoNamespace.vertical, "justify"));
+ break;
+ case Vertical.Top:
+ align.Add(new XAttribute(NoNamespace.vertical, "top"));
+ break;
+ }
+ if (WrapText)
+ align.Add(new XAttribute(NoNamespace.wrapText, true));
+ return align;
+ }
+ }
+
+ public static int GetStyleIndex(SpreadsheetDocument document, int numFmt, int font, int fill, int border, CellAlignment alignment, bool hidden, bool locked)
+ {
+ XElement xf = new XElement(S.xf, new XAttribute(NoNamespace.numFmtId, numFmt),
+ new XAttribute(NoNamespace.fontId, font), new XAttribute(NoNamespace.fillId, fill),
+ new XAttribute(NoNamespace.borderId, border), new XAttribute(NoNamespace.xfId, 0),
+ new XAttribute(NoNamespace.applyNumberFormat, (numFmt == 0) ? 0 : 1),
+ new XAttribute(NoNamespace.applyFont, (font == 0) ? 0 : 1),
+ new XAttribute(NoNamespace.applyFill, (fill == 0) ? 0 : 1),
+ new XAttribute(NoNamespace.applyBorder, (border == 0) ? 0 : 1));
+ if (alignment != null)
+ {
+ xf.Add(new XAttribute(NoNamespace.applyAlignment, "1"));
+ xf.Add(alignment.GetXElement());
+ }
+ else
+ xf.Add(new XAttribute(NoNamespace.applyAlignment, "0"));
+ if (hidden || locked)
+ {
+ XElement prot = new XElement(S.protection);
+ if (hidden)
+ prot.Add(new XAttribute(NoNamespace.hidden, true));
+ if (locked)
+ prot.Add(new XAttribute(NoNamespace.locked, true));
+ xf.Add(prot);
+ xf.Add(new XAttribute(NoNamespace.applyProtection, "1"));
+ }
+ else
+ xf.Add(new XAttribute(NoNamespace.applyProtection, "0"));
+
+ XDocument styles = document.WorkbookPart.WorkbookStylesPart.GetXDocument();
+ XElement cellXfs = styles.Root.Element(S.cellXfs);
+ int index = Array.FindIndex(cellXfs.Elements(S.xf).ToArray(),
+ z => XElement.DeepEquals(z, xf));
+ if (index != -1)
+ return index;
+ cellXfs.Add(xf);
+ cellXfs.Attribute(NoNamespace.count).Value = cellXfs.Elements(S.xf).Count().ToString();
+ document.WorkbookPart.WorkbookStylesPart.PutXDocument();
+ return cellXfs.Elements(S.xf).Count() - 1;
+ }
+
+ public static void CreateDefaultStyles(SpreadsheetDocument document)
+ {
+ // Create the style part
+ WorkbookStylesPart stylesPart = document.WorkbookPart.AddNewPart<WorkbookStylesPart>();
+ stylesPart.PutXDocument(new XDocument(XElement.Parse(
+@"<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<styleSheet xmlns='http://schemas.openxmlformats.org/spreadsheetml/2006/main'>
+ <fonts count='18'>
+ <font>
+ <sz val='11'/>
+ <color theme='1'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color theme='1'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='18'/>
+ <color theme='3'/>
+ <name val='Cambria'/>
+ <family val='2'/>
+ <scheme val='major'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='15'/>
+ <color theme='3'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='13'/>
+ <color theme='3'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='11'/>
+ <color theme='3'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color rgb='FF006100'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color rgb='FF9C0006'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color rgb='FF9C6500'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color rgb='FF3F3F76'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='11'/>
+ <color rgb='FF3F3F3F'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='11'/>
+ <color rgb='FFFA7D00'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color rgb='FFFA7D00'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='11'/>
+ <color theme='0'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color rgb='FFFF0000'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <i/>
+ <sz val='11'/>
+ <color rgb='FF7F7F7F'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <b/>
+ <sz val='11'/>
+ <color theme='1'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ <font>
+ <sz val='11'/>
+ <color theme='0'/>
+ <name val='Calibri'/>
+ <family val='2'/>
+ <scheme val='minor'/>
+ </font>
+ </fonts>
+ <fills count='33'>
+ <fill>
+ <patternFill patternType='none'/>
+ </fill>
+ <fill>
+ <patternFill patternType='gray125'/>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFC6EFCE'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFFFC7CE'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFFFEB9C'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFFFCC99'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFF2F2F2'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFA5A5A5'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor rgb='FFFFFFCC'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='4'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='4' tint='0.79998168889431442'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='4' tint='0.59999389629810485'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='4' tint='0.39997558519241921'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='5'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='5' tint='0.79998168889431442'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='5' tint='0.59999389629810485'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='5' tint='0.39997558519241921'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='6'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='6' tint='0.79998168889431442'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='6' tint='0.59999389629810485'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='6' tint='0.39997558519241921'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='7'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='7' tint='0.79998168889431442'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='7' tint='0.59999389629810485'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='7' tint='0.39997558519241921'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='8'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='8' tint='0.79998168889431442'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='8' tint='0.59999389629810485'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='8' tint='0.39997558519241921'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='9'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='9' tint='0.79998168889431442'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='9' tint='0.59999389629810485'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ <fill>
+ <patternFill patternType='solid'>
+ <fgColor theme='9' tint='0.39997558519241921'/>
+ <bgColor indexed='65'/>
+ </patternFill>
+ </fill>
+ </fills>
+ <borders count='10'>
+ <border>
+ <left/>
+ <right/>
+ <top/>
+ <bottom/>
+ <diagonal/>
+ </border>
+ <border>
+ <left/>
+ <right/>
+ <top/>
+ <bottom style='thick'>
+ <color theme='4'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left/>
+ <right/>
+ <top/>
+ <bottom style='thick'>
+ <color theme='4' tint='0.499984740745262'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left/>
+ <right/>
+ <top/>
+ <bottom style='medium'>
+ <color theme='4' tint='0.39997558519241921'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left style='thin'>
+ <color rgb='FF7F7F7F'/>
+ </left>
+ <right style='thin'>
+ <color rgb='FF7F7F7F'/>
+ </right>
+ <top style='thin'>
+ <color rgb='FF7F7F7F'/>
+ </top>
+ <bottom style='thin'>
+ <color rgb='FF7F7F7F'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left style='thin'>
+ <color rgb='FF3F3F3F'/>
+ </left>
+ <right style='thin'>
+ <color rgb='FF3F3F3F'/>
+ </right>
+ <top style='thin'>
+ <color rgb='FF3F3F3F'/>
+ </top>
+ <bottom style='thin'>
+ <color rgb='FF3F3F3F'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left/>
+ <right/>
+ <top/>
+ <bottom style='double'>
+ <color rgb='FFFF8001'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left style='double'>
+ <color rgb='FF3F3F3F'/>
+ </left>
+ <right style='double'>
+ <color rgb='FF3F3F3F'/>
+ </right>
+ <top style='double'>
+ <color rgb='FF3F3F3F'/>
+ </top>
+ <bottom style='double'>
+ <color rgb='FF3F3F3F'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left style='thin'>
+ <color rgb='FFB2B2B2'/>
+ </left>
+ <right style='thin'>
+ <color rgb='FFB2B2B2'/>
+ </right>
+ <top style='thin'>
+ <color rgb='FFB2B2B2'/>
+ </top>
+ <bottom style='thin'>
+ <color rgb='FFB2B2B2'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ <border>
+ <left/>
+ <right/>
+ <top style='thin'>
+ <color theme='4'/>
+ </top>
+ <bottom style='double'>
+ <color theme='4'/>
+ </bottom>
+ <diagonal/>
+ </border>
+ </borders>
+ <cellStyleXfs count='42'>
+ <xf numFmtId='0' fontId='0' fillId='0' borderId='0'/>
+ <xf numFmtId='0' fontId='2' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='3' fillId='0' borderId='1' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='4' fillId='0' borderId='2' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='5' fillId='0' borderId='3' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='5' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='6' fillId='2' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='7' fillId='3' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='8' fillId='4' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='9' fillId='5' borderId='4' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='10' fillId='6' borderId='5' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='11' fillId='6' borderId='4' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='12' fillId='0' borderId='6' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='13' fillId='7' borderId='7' applyNumberFormat='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='14' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='8' borderId='8' applyNumberFormat='0' applyFont='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='15' fillId='0' borderId='0' applyNumberFormat='0' applyFill='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='16' fillId='0' borderId='9' applyNumberFormat='0' applyFill='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='9' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='10' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='11' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='12' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='13' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='14' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='15' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='16' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='17' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='18' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='19' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='20' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='21' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='22' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='23' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='24' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='25' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='26' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='27' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='28' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='29' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='30' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='1' fillId='31' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ <xf numFmtId='0' fontId='17' fillId='32' borderId='0' applyNumberFormat='0' applyBorder='0' applyAlignment='0' applyProtection='0'/>
+ </cellStyleXfs>
+ <cellXfs count='1'>
+ <xf numFmtId='0' fontId='0' fillId='0' borderId='0' xfId='0'/>
+ </cellXfs>
+ <cellStyles count='42'>
+ <cellStyle name='20% - Accent1' xfId='19' builtinId='30' customBuiltin='1'/>
+ <cellStyle name='20% - Accent2' xfId='23' builtinId='34' customBuiltin='1'/>
+ <cellStyle name='20% - Accent3' xfId='27' builtinId='38' customBuiltin='1'/>
+ <cellStyle name='20% - Accent4' xfId='31' builtinId='42' customBuiltin='1'/>
+ <cellStyle name='20% - Accent5' xfId='35' builtinId='46' customBuiltin='1'/>
+ <cellStyle name='20% - Accent6' xfId='39' builtinId='50' customBuiltin='1'/>
+ <cellStyle name='40% - Accent1' xfId='20' builtinId='31' customBuiltin='1'/>
+ <cellStyle name='40% - Accent2' xfId='24' builtinId='35' customBuiltin='1'/>
+ <cellStyle name='40% - Accent3' xfId='28' builtinId='39' customBuiltin='1'/>
+ <cellStyle name='40% - Accent4' xfId='32' builtinId='43' customBuiltin='1'/>
+ <cellStyle name='40% - Accent5' xfId='36' builtinId='47' customBuiltin='1'/>
+ <cellStyle name='40% - Accent6' xfId='40' builtinId='51' customBuiltin='1'/>
+ <cellStyle name='60% - Accent1' xfId='21' builtinId='32' customBuiltin='1'/>
+ <cellStyle name='60% - Accent2' xfId='25' builtinId='36' customBuiltin='1'/>
+ <cellStyle name='60% - Accent3' xfId='29' builtinId='40' customBuiltin='1'/>
+ <cellStyle name='60% - Accent4' xfId='33' builtinId='44' customBuiltin='1'/>
+ <cellStyle name='60% - Accent5' xfId='37' builtinId='48' customBuiltin='1'/>
+ <cellStyle name='60% - Accent6' xfId='41' builtinId='52' customBuiltin='1'/>
+ <cellStyle name='Accent1' xfId='18' builtinId='29' customBuiltin='1'/>
+ <cellStyle name='Accent2' xfId='22' builtinId='33' customBuiltin='1'/>
+ <cellStyle name='Accent3' xfId='26' builtinId='37' customBuiltin='1'/>
+ <cellStyle name='Accent4' xfId='30' builtinId='41' customBuiltin='1'/>
+ <cellStyle name='Accent5' xfId='34' builtinId='45' customBuiltin='1'/>
+ <cellStyle name='Accent6' xfId='38' builtinId='49' customBuiltin='1'/>
+ <cellStyle name='Bad' xfId='7' builtinId='27' customBuiltin='1'/>
+ <cellStyle name='Calculation' xfId='11' builtinId='22' customBuiltin='1'/>
+ <cellStyle name='Check Cell' xfId='13' builtinId='23' customBuiltin='1'/>
+ <cellStyle name='Explanatory Text' xfId='16' builtinId='53' customBuiltin='1'/>
+ <cellStyle name='Good' xfId='6' builtinId='26' customBuiltin='1'/>
+ <cellStyle name='Heading 1' xfId='2' builtinId='16' customBuiltin='1'/>
+ <cellStyle name='Heading 2' xfId='3' builtinId='17' customBuiltin='1'/>
+ <cellStyle name='Heading 3' xfId='4' builtinId='18' customBuiltin='1'/>
+ <cellStyle name='Heading 4' xfId='5' builtinId='19' customBuiltin='1'/>
+ <cellStyle name='Input' xfId='9' builtinId='20' customBuiltin='1'/>
+ <cellStyle name='Linked Cell' xfId='12' builtinId='24' customBuiltin='1'/>
+ <cellStyle name='Neutral' xfId='8' builtinId='28' customBuiltin='1'/>
+ <cellStyle name='Normal' xfId='0' builtinId='0'/>
+ <cellStyle name='Note' xfId='15' builtinId='10' customBuiltin='1'/>
+ <cellStyle name='Output' xfId='10' builtinId='21' customBuiltin='1'/>
+ <cellStyle name='Title' xfId='1' builtinId='15' customBuiltin='1'/>
+ <cellStyle name='Total' xfId='17' builtinId='25' customBuiltin='1'/>
+ <cellStyle name='Warning Text' xfId='14' builtinId='11' customBuiltin='1'/>
+ </cellStyles>
+ <dxfs count='0'/>
+ <tableStyles count='0' defaultTableStyle='TableStyleMedium9' defaultPivotStyle='PivotStyleLight16'/>
+</styleSheet>")));
+ }
+
+ /// <summary>
+ /// Creates a worksheet document and inserts data into it
+ /// </summary>
+ /// <param name="headerList">List of values that will act as the header</param>
+ /// <param name="valueTable">Values for worksheet content</param>
+ /// <param name="headerRow">Header row</param>
+ /// <returns></returns>
+ internal static WorksheetPart Create(SpreadsheetDocument document, List<string> headerList, string[][] valueTable, int headerRow)
+ {
+ XDocument xDocument = CreateEmptyWorksheet();
+
+ for (int i = 0; i < headerList.Count; i++)
+ {
+ AddValue(xDocument, headerRow, i + 1, headerList[i]);
+ }
+ int rows = valueTable.GetLength(0);
+ int cols = valueTable[0].GetLength(0);
+
+ for (int i = 0; i < rows; i++)
+ {
+ for (int j = 0; j < cols; j++)
+ {
+ AddValue(xDocument, i + headerRow + 1, j + 1, valueTable[i][j]);
+ }
+ }
+ WorksheetPart part = Add(document, xDocument);
+ return part;
+ }
+
+ /// <summary>
+ /// Creates element structure needed to describe an empty worksheet
+ /// </summary>
+ /// <returns>Document with contents for an empty worksheet</returns>
+ private static XDocument CreateEmptyWorksheet()
+ {
+ XDocument document =
+ new XDocument(
+ new XElement(ns + "worksheet",
+ new XAttribute("xmlns", ns),
+ new XAttribute(XNamespace.Xmlns + "r", relationshipsns),
+ new XElement(ns + "sheetData")
+ )
+ );
+ return document;
+ }
+
+ /// <summary>
+ /// Adds a value to a cell inside a worksheet document
+ /// </summary>
+ /// <param name="worksheet">document to add values</param>
+ /// <param name="row">Row</param>
+ /// <param name="column">Column</param>
+ /// <param name="value">Value to add</param>
+ private static void AddValue(XDocument worksheet, int row, int column, string value)
+ {
+ //Set the cell reference
+ string cellReference = GetColumnId(column) + row.ToString();
+ double numericValue;
+ //Determining if value for cell is text or numeric
+ bool valueIsNumeric = double.TryParse(value, out numericValue);
+
+ //Creating the new cell element (markup)
+ XElement newCellXElement = valueIsNumeric ?
+ new XElement(ns + "c",
+ new XAttribute("r", cellReference),
+ new XElement(ns + "v", numericValue)
+ )
+ :
+ new XElement(ns + "c",
+ new XAttribute("r", cellReference),
+ new XAttribute("t", "inlineStr"),
+ new XElement(ns + "is",
+ new XElement(ns + "t", value)
+ )
+ );
+
+ // Find the row containing the cell to add the value to
+ XName rowName = "r";
+ XElement rowElement =
+ worksheet.Root
+ .Element(ns + "sheetData")
+ .Elements(ns + "row")
+ .Where(
+ t => t.Attribute(rowName).Value == row.ToString()
+ )
+ .FirstOrDefault();
+
+ if (rowElement == null)
+ {
+ //row element does not exist
+ //create a new one
+ rowElement = CreateEmptyRow(row);
+
+ //row elements must appear in order inside sheetData element
+ if (worksheet.Root
+ .Element(ns + "sheetData").HasElements)
+ { //if there are more rows already defined at sheetData element
+ //find the row with the inmediate higher index for the row containing the cell to set the value to
+ XElement rowAfterElement = FindRowAfter(worksheet, row);
+ //if there is a row with an inmediate higher index already defined at sheetData
+ if (rowAfterElement != null)
+ {
+ //add the new row before the row with an inmediate higher index
+ rowAfterElement.AddBeforeSelf(rowElement);
+ }
+ else
+ { //this row is going to be the one with the highest index (add it as the last element for sheetData)
+ worksheet.Root.Element(ns + "sheetData").Elements(ns + "row").Last().AddAfterSelf(rowElement);
+ }
+ }
+ else
+ { //there are no other rows already defined at sheetData
+ //Add a new row elemento to sheetData
+ worksheet
+ .Root
+ .Element(ns + "sheetData")
+ .Add(
+ rowElement //= CreateEmptyRow(row)
+ );
+ }
+
+ //Add the new cell to the row Element
+ rowElement.Add(newCellXElement);
+ }
+ else
+ {
+ //row containing the cell to set the value to is already defined at sheetData
+ //look if cell already exist at that row
+ XElement currentCellXElement = rowElement
+ .Elements(ns + "c")
+ .Where(
+ t => t.Attribute("r").Value == cellReference
+ ).FirstOrDefault();
+
+ if (currentCellXElement == null)
+ { //cell element does not exist at row indicated as parameter
+ //find the inmediate right column for the cell to set the value to
+ XElement columnAfterXElement = FindColumAfter(worksheet, row, column);
+ if (columnAfterXElement != null)
+ {
+ //Insert the new cell before the inmediate right column
+ columnAfterXElement.AddBeforeSelf(newCellXElement);
+ }
+ else
+ { //There is no inmediate right cell
+ //Add the new cell as the last element for the row
+ rowElement.Add(newCellXElement);
+ }
+ }
+ else
+ {
+ //cell alreay exist
+ //replace the current cell with that with the new value
+ currentCellXElement.ReplaceWith(newCellXElement);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Adds a given worksheet to the document
+ /// </summary>
+ /// <param name="worksheet">Worksheet document to add</param>
+ /// <returns>Worksheet part just added</returns>
+ public static WorksheetPart Add(SpreadsheetDocument doc, XDocument worksheet)
+ {
+ // Associates base content to a new worksheet part
+ WorkbookPart workbook = doc.WorkbookPart;
+ WorksheetPart worksheetPart = workbook.AddNewPart<WorksheetPart>();
+ worksheetPart.PutXDocument(worksheet);
+
+ // Associates the worksheet part to the workbook part
+ XDocument document = doc.WorkbookPart.GetXDocument();
+ int sheetId =
+ document.Root
+ .Element(ns + "sheets")
+ .Elements(ns + "sheet")
+ .Count() + 1;
+
+ int worksheetCount =
+ document.Root
+ .Element(ns + "sheets")
+ .Elements(ns + "sheet")
+ .Where(
+ t =>
+ t.Attribute("name").Value.StartsWith("sheet", StringComparison.OrdinalIgnoreCase)
+ )
+ .Count() + 1;
+
+ // Adds content to workbook document to reference worksheet document
+ document.Root
+ .Element(ns + "sheets")
+ .Add(
+ new XElement(ns + "sheet",
+ new XAttribute("name", string.Format("sheet{0}", worksheetCount)),
+ new XAttribute("sheetId", sheetId),
+ new XAttribute(relationshipsns + "id", workbook.GetIdOfPart(worksheetPart))
+ )
+ );
+ doc.WorkbookPart.PutXDocument();
+ return worksheetPart;
+ }
+ }
+}
diff --git a/OpenXmlPowerTools/XlsxTables.cs b/OpenXmlPowerTools/XlsxTables.cs
new file mode 100644
index 0000000..a87918d
--- /dev/null
+++ b/OpenXmlPowerTools/XlsxTables.cs
@@ -0,0 +1,478 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using System.IO;
+
+namespace OpenXmlPowerTools
+{
+ public class Table
+ {
+ public int Id { get; set; }
+ public string TableName { get; set; }
+ public string DisplayName { get; set; }
+ public XElement TableStyleInfo { get; set; }
+ public string Ref { get; set; }
+ public int LeftColumn { get; set; }
+ public int RightColumn { get; set; }
+ public int TopRow { get; set; }
+ public int BottomRow { get; set; }
+ public int? HeaderRowCount { get; set; }
+ public int? TotalsRowCount { get; set; }
+ public string TableType { get; set; } // external data query, data in worksheet, or XML data
+ public TableDefinitionPart TableDefinitionPart { get; set; }
+ public WorksheetPart Parent { get; set; }
+ public Table(WorksheetPart parent) { Parent = parent; }
+ public IEnumerable<TableColumn> TableColumns()
+ {
+ XNamespace x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ return TableDefinitionPart
+ .GetXDocument()
+ .Root
+ .Element(x + "tableColumns")
+ .Elements(x + "tableColumn")
+ .Select((c, i) =>
+ new TableColumn(this)
+ {
+ Id = (int)c.Attribute("id"),
+ ColumnNumber = this.LeftColumn + i,
+ Name = (string)c.Attribute("name"),
+ DataDxfId = (int?)c.Attribute("dataDxfId"),
+ QueryTableFieldId = (int?)c.Attribute("queryTableFieldId"),
+ UniqueName = (string)c.Attribute("uniqueName"),
+ ColumnIndex = i,
+ }
+ );
+ }
+ public IEnumerable<TableRow> TableRows()
+ {
+ string refStart = Ref.Split(':').First();
+ int rowStart = Int32.Parse(XlsxTables.SplitAddress(refStart)[1]);
+ string refEnd = Ref.Split(':').ElementAt(1);
+ int rowEnd = Int32.Parse(XlsxTables.SplitAddress(refEnd)[1]);
+ int headerRowsCount = HeaderRowCount == null ? 0 : (int)HeaderRowCount;
+ int totalRowsCount = TotalsRowCount == null ? 0 : (int)TotalsRowCount;
+ return Parent
+ .Rows()
+ .Skip(headerRowsCount)
+ .SkipLast(totalRowsCount)
+ .Where(r =>
+ {
+ int rowId = Int32.Parse(r.RowId);
+ return rowId >= rowStart && rowId <= rowEnd;
+ }
+ )
+ .Select(r => new TableRow(this) { Row = r });
+ }
+ }
+
+ public class TableColumn
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public int? DataDxfId { get; set; }
+ public int? QueryTableFieldId { get; set; }
+ public string UniqueName { get; set; }
+ public int ColumnNumber { get; set; }
+ public int ColumnIndex { get; set; }
+ public Table Parent { get; set; }
+ public TableColumn(Table parent) { Parent = parent; }
+ }
+
+ public class TableRow
+ {
+ public Row Row { get; set; }
+ public Table Parent { get; set; }
+ public TableRow(Table parent) { Parent = parent; }
+ public TableCell this[string columnName]
+ {
+ get
+ {
+ TableColumn tc = Parent
+ .TableColumns()
+ .Where(x => x.Name.ToLower() == columnName.ToLower())
+ .FirstOrDefault();
+ if (tc == null)
+ throw new Exception("Invalid column name: " + columnName);
+ string[] refs = Parent.Ref.Split(':');
+ string[] startRefs = XlsxTables.SplitAddress(refs[0]);
+ string columnAddress = XlsxTables.IndexToColumnAddress(XlsxTables.ColumnAddressToIndex(startRefs[0]) + tc.ColumnIndex);
+ Cell cell = Row.Cells().Where(c => c.ColumnAddress == columnAddress).FirstOrDefault();
+ if (cell != null)
+ {
+ if (cell.Type == "s")
+ return new TableCell(cell.SharedString);
+ else
+ return new TableCell(cell.Value);
+ }
+ else
+ return new TableCell("");
+ }
+ }
+ }
+
+ public class TableCell : IEquatable<TableCell>
+ {
+ public string Value { get; set; }
+ public TableCell(string v)
+ {
+ Value = v;
+ }
+ public override string ToString()
+ {
+ return Value;
+ }
+ public override bool Equals(object obj)
+ {
+ return this.Value == ((TableCell)obj).Value;
+ }
+ bool IEquatable<TableCell>.Equals(TableCell other)
+ {
+ return this.Value == other.Value;
+ }
+ public override int GetHashCode()
+ {
+ return this.Value.GetHashCode();
+ }
+ public static bool operator ==(TableCell left, TableCell right)
+ {
+ if ((object)left != (object)right) return false;
+ return left.Value == right.Value;
+ }
+ public static bool operator !=(TableCell left, TableCell right)
+ {
+ if ((object)left != (object)right) return false;
+ return left.Value != right.Value;
+ }
+ public static explicit operator string(TableCell cell)
+ {
+ if (cell == null) return null;
+ return cell.Value;
+ }
+ public static explicit operator bool(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return cell.Value == "1";
+ }
+ public static explicit operator bool?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return cell.Value == "1";
+ }
+ public static explicit operator int(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return Int32.Parse(cell.Value);
+ }
+ public static explicit operator int?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return Int32.Parse(cell.Value);
+ }
+ public static explicit operator uint(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return UInt32.Parse(cell.Value);
+ }
+ public static explicit operator uint?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return UInt32.Parse(cell.Value);
+ }
+ public static explicit operator long(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return Int64.Parse(cell.Value);
+ }
+ public static explicit operator long?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return Int64.Parse(cell.Value);
+ }
+ public static explicit operator ulong(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return UInt64.Parse(cell.Value);
+ }
+ public static explicit operator ulong?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return UInt64.Parse(cell.Value);
+ }
+ public static explicit operator float(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return Single.Parse(cell.Value);
+ }
+ public static explicit operator float?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return Single.Parse(cell.Value);
+ }
+ public static explicit operator double(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return Double.Parse(cell.Value);
+ }
+ public static explicit operator double?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return Double.Parse(cell.Value);
+ }
+ public static explicit operator decimal(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return Decimal.Parse(cell.Value);
+ }
+ public static explicit operator decimal?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return Decimal.Parse(cell.Value);
+ }
+ public static implicit operator DateTime(TableCell cell)
+ {
+ if (cell == null) throw new ArgumentNullException("TableCell");
+ return new DateTime(1900, 1, 1).AddDays(Int32.Parse(cell.Value) - 2);
+ }
+ public static implicit operator DateTime?(TableCell cell)
+ {
+ if (cell == null) return null;
+ return new DateTime(1900, 1, 1).AddDays(Int32.Parse(cell.Value) - 2);
+ }
+ }
+
+ public class Row
+ {
+ public XElement RowElement { get; set; }
+ public string RowId { get; set; }
+ public string Spans { get; set; }
+ public List<Cell> Cells()
+ {
+ XNamespace s = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ SpreadsheetDocument doc = (SpreadsheetDocument)Parent.OpenXmlPackage;
+ SharedStringTablePart sharedStringTable = doc.WorkbookPart.SharedStringTablePart;
+ IEnumerable<XElement> cells = this.RowElement.Elements(S.c);
+ var r = cells
+ .Select(cell => {
+ var cellType = (string)cell.Attribute("t");
+ var sharedString = cellType == "s" ?
+ sharedStringTable
+ .GetXDocument()
+ .Root
+ .Elements(s + "si")
+ .Skip((int)cell.Element(s + "v"))
+ .First()
+ .Descendants(s + "t")
+ .StringConcatenate(e => (string)e)
+ : null;
+ var column = (string)cell.Attribute("r");
+ var columnAddress = column.Split('0', '1', '2', '3', '4', '5', '6', '7', '8', '9').First();
+ var columnIndex = XlsxTables.ColumnAddressToIndex(columnAddress);
+ var newCell = new Cell(this)
+ {
+ CellElement = cell,
+ Row = (string)RowElement.Attribute("r"),
+ Column = column,
+ ColumnAddress = columnAddress,
+ ColumnIndex = columnIndex,
+ Type = cellType,
+ Formula = (string)cell.Element(S.f),
+ Style = (int?)cell.Attribute("s"),
+ Value = (string)cell.Element(S.v),
+ SharedString = sharedString
+ };
+ return newCell;
+ });
+ var ra = r.ToList();
+ return ra;
+ }
+ public WorksheetPart Parent { get; set; }
+ public Row(WorksheetPart parent) { Parent = parent; }
+ }
+
+ public class Cell
+ {
+ public XElement CellElement { get; set; }
+ public string Row { get; set; }
+ public string Column { get; set; }
+ public string ColumnAddress { get; set; }
+ public int ColumnIndex { get; set; }
+ public string Type { get; set; }
+ public string Value { get; set; }
+ public string Formula { get; set; }
+ public int? Style { get; set; }
+ public string SharedString { get; set; }
+ public Row Parent { get; set; }
+ public Cell(Row parent) { Parent = parent; }
+ }
+
+ public static class XlsxTables
+ {
+ public static IEnumerable<Table> Tables(this SpreadsheetDocument spreadsheet)
+ {
+ foreach (var worksheetPart in spreadsheet.WorkbookPart.WorksheetParts)
+ foreach (var table in worksheetPart.TableDefinitionParts)
+ {
+ XDocument tableDefDoc = table.GetXDocument();
+
+ Table t = new Table(worksheetPart)
+ {
+ Id = (int)tableDefDoc.Root.Attribute("id"),
+ TableName = (string)tableDefDoc.Root.Attribute("name"),
+ DisplayName = (string)tableDefDoc.Root.Attribute("displayName"),
+ TableStyleInfo = tableDefDoc.Root.Element(S.tableStyleInfo),
+ Ref = (string)tableDefDoc.Root.Attribute("ref"),
+ TotalsRowCount = (int?)tableDefDoc.Root.Attribute("totalsRowCount"),
+ //HeaderRowCount = (int?)tableDefDoc.Root.Attribute("headerRowCount"),
+ HeaderRowCount = 1, // currently there always is a header row
+ TableType = (string)tableDefDoc.Root.Attribute("tableType"),
+ TableDefinitionPart = table
+ };
+ int leftColumn, topRow, rightColumn, bottomRow;
+ ParseRange(t.Ref, out leftColumn, out topRow, out rightColumn, out bottomRow);
+ t.LeftColumn = leftColumn;
+ t.TopRow = topRow;
+ t.RightColumn = rightColumn;
+ t.BottomRow = bottomRow;
+ yield return t;
+ }
+ }
+
+ public static void ParseRange(string theRef, out int leftColumn, out int topRow, out int rightColumn, out int bottomRow)
+ {
+ // C5:E7
+ var spl = theRef.Split(':');
+ string refStart = spl.First();
+ var refStartSplit = XlsxTables.SplitAddress(refStart);
+ leftColumn = XlsxTables.ColumnAddressToIndex(refStartSplit[0]);
+ topRow = Int32.Parse(refStartSplit[1]);
+
+ string refEnd = spl.ElementAt(1);
+ var refEndSplit = XlsxTables.SplitAddress(refEnd);
+ rightColumn = XlsxTables.ColumnAddressToIndex(refEndSplit[0]);
+ bottomRow = Int32.Parse(refEndSplit[1]);
+ }
+
+ public static Table Table(this SpreadsheetDocument spreadsheet,
+ string tableName)
+ {
+ return spreadsheet.Tables().Where(t => t.TableName.ToLower() == tableName.ToLower()).FirstOrDefault();
+ }
+
+ public static IEnumerable<Row> Rows(this WorksheetPart worksheetPart)
+ {
+ XNamespace s = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
+ var rows = worksheetPart
+ .GetXDocument()
+ .Root
+ .Elements(S.sheetData)
+ .Elements(S.row)
+ .Select(r =>
+ {
+ var row = new Row(worksheetPart)
+ {
+ RowElement = r,
+ RowId = (string)r.Attribute("r"),
+ Spans = (string)r.Attribute("spans")
+ };
+ return row;
+ });
+ return rows;
+ }
+
+ public static string[] SplitAddress(string address)
+ {
+ int i;
+ for (i = 0; i < address.Length; i++)
+ if (address[i] >= '0' && address[i] <= '9')
+ break;
+ if (i == address.Length)
+ throw new FileFormatException("Invalid spreadsheet. Bad cell address.");
+ return new[] {
+ address.Substring(0, i),
+ address.Substring(i)
+ };
+ }
+
+ public static string IndexToColumnAddress(int index)
+ {
+ if (index < 26)
+ {
+ char c = (char)((int)'A' + index);
+ string s = new string(c, 1);
+ return s;
+ }
+ if (index < 702)
+ {
+ int i = index - 26;
+ int i1 = (int)(i / 26);
+ int i2 = i % 26;
+ string s = new string((char)((int)'A' + i1), 1) +
+ new string((char)((int)'A' + i2), 1);
+ return s;
+ }
+ if (index < 18278)
+ {
+ int i = index - 702;
+ int i1 = (int)(i / 676);
+ i = i - i1 * 676;
+ int i2 = (int)(i / 26);
+ int i3 = i % 26;
+ string s = new string((char)((int)'A' + i1), 1) +
+ new string((char)((int)'A' + i2), 1) +
+ new string((char)((int)'A' + i3), 1);
+ return s;
+ }
+ throw new Exception("Invalid column address");
+ }
+
+ public static int ColumnAddressToIndex(string columnAddress)
+ {
+ if (columnAddress.Length == 1)
+ {
+ char c = columnAddress[0];
+ int i = c - 'A';
+ return i;
+ }
+ if (columnAddress.Length == 2)
+ {
+ char c1 = columnAddress[0];
+ char c2 = columnAddress[1];
+ int i1 = c1 - 'A';
+ int i2 = c2 - 'A';
+ return (i1 + 1) * 26 + i2;
+ }
+ if (columnAddress.Length == 3)
+ {
+ char c1 = columnAddress[0];
+ char c2 = columnAddress[1];
+ char c3 = columnAddress[2];
+ int i1 = c1 - 'A';
+ int i2 = c2 - 'A';
+ int i3 = c3 - 'A';
+ return (i1 + 1) * 676 + (i2 + 1) * 26 + i3;
+ }
+ throw new FileFormatException("Invalid spreadsheet: Invalid column address.");
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-01.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-01.docx
new file mode 100644
index 0000000..2cb7a30
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-02.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-02.docx
new file mode 100644
index 0000000..60abe27
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-03.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-03.docx
new file mode 100644
index 0000000..dd95819
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-04.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-04.docx
new file mode 100644
index 0000000..582edac
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-05.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-05.docx
new file mode 100644
index 0000000..ca19068
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-06.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-06.docx
new file mode 100644
index 0000000..93d591a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-07.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-07.docx
new file mode 100644
index 0000000..cca887d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-08.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-08.docx
new file mode 100644
index 0000000..428f74e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-08.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-41.pptx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-41.pptx
new file mode 100644
index 0000000..6e80384
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Cached-Data-41.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-01.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-01.docx
new file mode 100644
index 0000000..e813a05
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-02.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-02.docx
new file mode 100644
index 0000000..704bbef
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-03.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-03.docx
new file mode 100644
index 0000000..c9c8c62
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-04.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-04.docx
new file mode 100644
index 0000000..d1578bc
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-05.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-05.docx
new file mode 100644
index 0000000..514befd
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-06.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-06.docx
new file mode 100644
index 0000000..ad76f7d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-07.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-07.docx
new file mode 100644
index 0000000..7132102
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-08.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-08.docx
new file mode 100644
index 0000000..f4e3d32
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-08.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-10.docx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-10.docx
new file mode 100644
index 0000000..7560ba4
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-10.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-41.pptx b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-41.pptx
new file mode 100644
index 0000000..3796212
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Chart-Embedded-Xlsx-41.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/ChartUpdater01.cs b/OpenXmlPowerToolsExamples/ChartUpdater01/ChartUpdater01.cs
new file mode 100644
index 0000000..80e88a1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/ChartUpdater01.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ var sourceDi = new DirectoryInfo("../../");
+ foreach (var file in sourceDi.GetFiles("*.docx"))
+ File.Copy(file.FullName, Path.Combine(tempDi.FullName, file.Name));
+ foreach (var file in sourceDi.GetFiles("*.pptx"))
+ File.Copy(file.FullName, Path.Combine(tempDi.FullName, file.Name));
+
+ var fileList = Directory.GetFiles(tempDi.FullName, "*.docx");
+ foreach (var file in fileList)
+ {
+ var fi = new FileInfo(file);
+ Console.WriteLine(fi.Name);
+ var newFileName = "Updated-" + fi.Name;
+ var fi2 = new FileInfo(Path.Combine(tempDi.FullName, newFileName));
+ File.Copy(fi.FullName, fi2.FullName);
+
+ using (var wDoc = WordprocessingDocument.Open(fi2.FullName, true))
+ {
+ var chart1Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Car",
+ "Truck",
+ "Van",
+ "Bike",
+ "Boat",
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Q1",
+ "Q2",
+ "Q3",
+ "Q4",
+ },
+ Values = new double[][] {
+ new double[] {
+ 100, 310, 220, 450,
+ },
+ new double[] {
+ 200, 300, 350, 411,
+ },
+ new double[] {
+ 80, 120, 140, 600,
+ },
+ new double[] {
+ 120, 100, 140, 400,
+ },
+ new double[] {
+ 200, 210, 210, 480,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart1", chart1Data);
+
+ var chart2Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Series"
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Cars",
+ "Trucks",
+ "Vans",
+ "Boats",
+ },
+ Values = new double[][] {
+ new double[] {
+ 320, 112, 64, 80,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart2", chart2Data);
+
+ var chart3Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "X1",
+ "X2",
+ "X3",
+ "X4",
+ "X5",
+ "X6",
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Y1",
+ "Y2",
+ "Y3",
+ "Y4",
+ "Y5",
+ "Y6",
+ },
+ Values = new double[][] {
+ new double[] { 3.0, 2.1, .7, .7, 2.1, 3.0, },
+ new double[] { 3.0, 2.1, .8, .8, 2.1, 3.0, },
+ new double[] { 3.0, 2.4, 1.2, 1.2, 2.4, 3.0, },
+ new double[] { 3.0, 2.7, 1.7, 1.7, 2.7, 3.0, },
+ new double[] { 3.0, 2.9, 2.5, 2.5, 2.9, 3.0, },
+ new double[] { 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart3", chart3Data);
+
+ var chart4Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Car",
+ "Truck",
+ "Van",
+ },
+ CategoryDataType = ChartDataType.DateTime,
+ CategoryFormatCode = 14,
+ CategoryNames = new[] {
+ ToExcelInteger(new DateTime(2013, 9, 1)),
+ ToExcelInteger(new DateTime(2013, 9, 2)),
+ ToExcelInteger(new DateTime(2013, 9, 3)),
+ ToExcelInteger(new DateTime(2013, 9, 4)),
+ ToExcelInteger(new DateTime(2013, 9, 5)),
+ ToExcelInteger(new DateTime(2013, 9, 6)),
+ ToExcelInteger(new DateTime(2013, 9, 7)),
+ ToExcelInteger(new DateTime(2013, 9, 8)),
+ ToExcelInteger(new DateTime(2013, 9, 9)),
+ ToExcelInteger(new DateTime(2013, 9, 10)),
+ ToExcelInteger(new DateTime(2013, 9, 11)),
+ ToExcelInteger(new DateTime(2013, 9, 12)),
+ ToExcelInteger(new DateTime(2013, 9, 13)),
+ ToExcelInteger(new DateTime(2013, 9, 14)),
+ ToExcelInteger(new DateTime(2013, 9, 15)),
+ ToExcelInteger(new DateTime(2013, 9, 16)),
+ ToExcelInteger(new DateTime(2013, 9, 17)),
+ ToExcelInteger(new DateTime(2013, 9, 18)),
+ ToExcelInteger(new DateTime(2013, 9, 19)),
+ ToExcelInteger(new DateTime(2013, 9, 20)),
+ },
+ Values = new double[][] {
+ new double[] {
+ 1, 2, 3, 2, 3, 4, 5, 4, 5, 6, 5, 4, 5, 6, 7, 8, 7, 8, 8, 9,
+ },
+ new double[] {
+ 2, 3, 3, 4, 4, 5, 6, 7, 8, 7, 8, 9, 9, 9, 7, 8, 9, 9, 10, 11,
+ },
+ new double[] {
+ 2, 3, 3, 3, 3, 2, 2, 2, 3, 2, 3, 3, 4, 4, 4, 3, 4, 5, 5, 4,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(wDoc, "Chart4", chart4Data);
+ }
+ }
+
+ fileList = Directory.GetFiles(tempDi.FullName, "*.pptx");
+ foreach (var file in fileList)
+ {
+ var fi = new FileInfo(file);
+ Console.WriteLine(fi.Name);
+ var newFileName = "Updated-" + fi.Name;
+ var fi2 = new FileInfo(Path.Combine(tempDi.FullName, newFileName));
+ File.Copy(fi.FullName, fi2.FullName);
+
+ using (var pDoc = PresentationDocument.Open(fi2.FullName, true))
+ {
+ var chart1Data = new ChartData
+ {
+ SeriesNames = new[] {
+ "Car",
+ "Truck",
+ "Van",
+ },
+ CategoryDataType = ChartDataType.String,
+ CategoryNames = new[] {
+ "Q1",
+ "Q2",
+ "Q3",
+ "Q4",
+ },
+ Values = new double[][] {
+ new double[] {
+ 320, 310, 320, 330,
+ },
+ new double[] {
+ 201, 224, 230, 221,
+ },
+ new double[] {
+ 180, 200, 220, 230,
+ },
+ },
+ };
+ ChartUpdater.UpdateChart(pDoc, 1, chart1Data);
+ }
+ }
+ }
+
+ private static string ToExcelInteger(DateTime dateTime)
+ {
+ return dateTime.ToOADate().ToString();
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/ChartUpdater01.csproj b/OpenXmlPowerToolsExamples/ChartUpdater01/ChartUpdater01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/ChartUpdater01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/ChartUpdater01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/ChartUpdater01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ChartUpdater01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler/Data.xml b/OpenXmlPowerToolsExamples/DocumentAssembler/Data.xml
new file mode 100644
index 0000000..64cc21e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler/Data.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Tai Yee</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order Number="1">
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>September 5, 2001</OrderDate>
+ </Order>
+ <Order Number="2">
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>8</Quantity>
+ <OrderDate>September 26, 2001</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler/DocumentAssembler.cs b/OpenXmlPowerToolsExamples/DocumentAssembler/DocumentAssembler.cs
new file mode 100644
index 0000000..fcd82a0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler/DocumentAssembler.cs
@@ -0,0 +1,57 @@
+using System;
+using System.IO;
+using System.Xml.Linq;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ if (args.Length != 3)
+ {
+ PrintUsage();
+ Environment.Exit(0);
+ }
+
+ FileInfo templateDoc = new FileInfo(args[0]);
+ if (!templateDoc.Exists)
+ {
+ Console.WriteLine("Error, {0} does not exist.", args[0]);
+ PrintUsage();
+ Environment.Exit(0);
+ }
+ FileInfo dataFile = new FileInfo(args[1]);
+ if (!dataFile.Exists)
+ {
+ Console.WriteLine("Error, {0} does not exist.", args[1]);
+ PrintUsage();
+ Environment.Exit(0);
+ }
+ FileInfo assembledDoc = new FileInfo(args[2]);
+ if (assembledDoc.Exists)
+ {
+ Console.WriteLine("Error, {0} exists.", args[2]);
+ PrintUsage();
+ Environment.Exit(0);
+ }
+
+ WmlDocument wmlDoc = new WmlDocument(templateDoc.FullName);
+ XElement data = XElement.Load(dataFile.FullName);
+ bool templateError;
+ WmlDocument wmlAssembledDoc = DocumentAssembler.AssembleDocument(wmlDoc, data, out templateError);
+ if (templateError)
+ {
+ Console.WriteLine("Errors in template.");
+ Console.WriteLine("See {0} to determine the errors in the template.", assembledDoc.Name);
+ }
+
+ wmlAssembledDoc.SaveAs(assembledDoc.FullName);
+ }
+
+ static void PrintUsage()
+ {
+ Console.WriteLine("Usage: DocumentAssembler TemplateDocument.docx Data.xml AssembledDoc.docx");
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler/DocumentAssembler.csproj b/OpenXmlPowerToolsExamples/DocumentAssembler/DocumentAssembler.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler/DocumentAssembler.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler/TemplateDocument.docx b/OpenXmlPowerToolsExamples/DocumentAssembler/TemplateDocument.docx
new file mode 100644
index 0000000..bbabcdc
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler/TemplateDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler01/Data.xml b/OpenXmlPowerToolsExamples/DocumentAssembler01/Data.xml
new file mode 100644
index 0000000..64cc21e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler01/Data.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Tai Yee</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order Number="1">
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>September 5, 2001</OrderDate>
+ </Order>
+ <Order Number="2">
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>8</Quantity>
+ <OrderDate>September 26, 2001</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler01/DocumentAssembler01.cs b/OpenXmlPowerToolsExamples/DocumentAssembler01/DocumentAssembler01.cs
new file mode 100644
index 0000000..7a9c723
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler01/DocumentAssembler01.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ FileInfo templateDoc = new FileInfo("../../TemplateDocument.docx");
+ FileInfo dataFile = new FileInfo("../../Data.xml");
+
+ WmlDocument wmlDoc = new WmlDocument(templateDoc.FullName);
+ XElement data = XElement.Load(dataFile.FullName);
+ bool templateError;
+ WmlDocument wmlAssembledDoc = DocumentAssembler.AssembleDocument(wmlDoc, data, out templateError);
+ if (templateError)
+ {
+ Console.WriteLine("Errors in template.");
+ Console.WriteLine("See AssembledDoc.docx to determine the errors in the template.");
+ }
+
+ FileInfo assembledDoc = new FileInfo(Path.Combine(tempDi.FullName, "AssembledDoc.docx"));
+ wmlAssembledDoc.SaveAs(assembledDoc.FullName);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler01/DocumentAssembler01.csproj b/OpenXmlPowerToolsExamples/DocumentAssembler01/DocumentAssembler01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler01/DocumentAssembler01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/DocumentAssembler01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler01/TemplateDocument.docx b/OpenXmlPowerToolsExamples/DocumentAssembler01/TemplateDocument.docx
new file mode 100644
index 0000000..913fad0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler01/TemplateDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler02/AssembledDoc.docx b/OpenXmlPowerToolsExamples/DocumentAssembler02/AssembledDoc.docx
new file mode 100644
index 0000000..6975e22
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler02/AssembledDoc.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler02/Data.xml b/OpenXmlPowerToolsExamples/DocumentAssembler02/Data.xml
new file mode 100644
index 0000000..64cc21e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler02/Data.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Tai Yee</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order Number="1">
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>September 5, 2001</OrderDate>
+ </Order>
+ <Order Number="2">
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>8</Quantity>
+ <OrderDate>September 26, 2001</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler02/DocumentAssembler02.cs b/OpenXmlPowerToolsExamples/DocumentAssembler02/DocumentAssembler02.cs
new file mode 100644
index 0000000..7a9c723
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler02/DocumentAssembler02.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ FileInfo templateDoc = new FileInfo("../../TemplateDocument.docx");
+ FileInfo dataFile = new FileInfo("../../Data.xml");
+
+ WmlDocument wmlDoc = new WmlDocument(templateDoc.FullName);
+ XElement data = XElement.Load(dataFile.FullName);
+ bool templateError;
+ WmlDocument wmlAssembledDoc = DocumentAssembler.AssembleDocument(wmlDoc, data, out templateError);
+ if (templateError)
+ {
+ Console.WriteLine("Errors in template.");
+ Console.WriteLine("See AssembledDoc.docx to determine the errors in the template.");
+ }
+
+ FileInfo assembledDoc = new FileInfo(Path.Combine(tempDi.FullName, "AssembledDoc.docx"));
+ wmlAssembledDoc.SaveAs(assembledDoc.FullName);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler02/DocumentAssembler02.csproj b/OpenXmlPowerToolsExamples/DocumentAssembler02/DocumentAssembler02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler02/DocumentAssembler02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler02/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/DocumentAssembler02/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler02/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler02/TemplateDocument.docx b/OpenXmlPowerToolsExamples/DocumentAssembler02/TemplateDocument.docx
new file mode 100644
index 0000000..45e8c99
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler02/TemplateDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler03/DocumentAssembler03.cs b/OpenXmlPowerToolsExamples/DocumentAssembler03/DocumentAssembler03.cs
new file mode 100644
index 0000000..0b8ee27
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler03/DocumentAssembler03.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ FileInfo templateDoc = new FileInfo("../../TemplateDocument.docx");
+ FileInfo dataFile = new FileInfo(Path.Combine(tempDi.FullName, "Data.xml"));
+
+ // The following method generates a large data file with random data.
+ // In a real world scenario, this is where you would query your data source and produce XML that will drive your document generation process.
+ XElement data = GenerateDataFromDataSource(dataFile);
+
+ WmlDocument wmlDoc = new WmlDocument(templateDoc.FullName);
+ int count = 1;
+ foreach (var customer in data.Elements("Customer"))
+ {
+ FileInfo assembledDoc = new FileInfo(Path.Combine(tempDi.FullName, string.Format("Letter-{0:0000}.docx", count++)));
+ Console.WriteLine(assembledDoc.Name);
+ bool templateError;
+ WmlDocument wmlAssembledDoc = DocumentAssembler.AssembleDocument(wmlDoc, customer, out templateError);
+ if (templateError)
+ {
+ Console.WriteLine("Errors in template.");
+ Console.WriteLine("See {0} to determine the errors in the template.", assembledDoc.Name);
+ }
+ wmlAssembledDoc.SaveAs(assembledDoc.FullName);
+ }
+ }
+
+ private static string[] s_productNames = new[] {
+ "Unicycle",
+ "Bicycle",
+ "Tricycle",
+ "Skateboard",
+ "Roller Blades",
+ "Hang Glider",
+ };
+
+ private static XElement GenerateDataFromDataSource(FileInfo dataFi)
+ {
+ int numberOfDocumentsToGenerate = 500;
+ var customers = new XElement("Customers");
+ Random r = new Random();
+ for (int i = 0; i < numberOfDocumentsToGenerate; ++i)
+ {
+ var customer = new XElement("Customer",
+ new XElement("CustomerID", i + 1),
+ new XElement("Name", "Eric White"),
+ new XElement("HighValueCustomer", r.Next(2) == 0 ? "True" : "False"),
+ new XElement("Orders"));
+ var orders = customer.Element("Orders");
+ int numberOfOrders = r.Next(10) + 1;
+ for (int j = 0; j < numberOfOrders; j++)
+ {
+ var order = new XElement("Order",
+ new XAttribute("Number", j + 1),
+ new XElement("ProductDescription", s_productNames[r.Next(s_productNames.Length)]),
+ new XElement("Quantity", r.Next(10)),
+ new XElement("OrderDate", "September 26, 2015"));
+ orders.Add(order);
+ }
+ customers.Add(customer);
+ }
+ customers.Save(dataFi.FullName);
+ return customers;
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler03/DocumentAssembler03.csproj b/OpenXmlPowerToolsExamples/DocumentAssembler03/DocumentAssembler03.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler03/DocumentAssembler03.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentAssembler03/TemplateDocument.docx b/OpenXmlPowerToolsExamples/DocumentAssembler03/TemplateDocument.docx
new file mode 100644
index 0000000..fd65569
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentAssembler03/TemplateDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder01/DocumentBuilder01.cs b/OpenXmlPowerToolsExamples/DocumentBuilder01/DocumentBuilder01.cs
new file mode 100644
index 0000000..3a94e39
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder01/DocumentBuilder01.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+
+namespace DocumentBuilderExample
+{
+ class DocumentBuilderExample
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ string source1 = "../../Source1.docx";
+ string source2 = "../../Source2.docx";
+ string source3 = "../../Source3.docx";
+ List<Source> sources = null;
+
+ // Create new document from 10 paragraphs starting at paragraph 5 of Source1.docx
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1), 5, 10, true),
+ };
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "Out1.docx"));
+
+ // Create new document from paragraph 1, and paragraphs 5 through end of Source3.docx.
+ // This effectively 'deletes' paragraphs 2-4
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source3), 0, 1, false),
+ new Source(new WmlDocument(source3), 4, false),
+ };
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "Out2.docx"));
+
+ // Create a new document that consists of the entirety of Source1.docx and Source2.docx. Use
+ // the section information (headings and footers) from source1.
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1), true),
+ new Source(new WmlDocument(source2), false),
+ };
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "Out3.docx"));
+
+ // Create a new document that consists of the entirety of Source1.docx and Source2.docx. Use
+ // the section information (headings and footers) from source2.
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1), false),
+ new Source(new WmlDocument(source2), true),
+ };
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "Out4.docx"));
+
+ // Create a new document that consists of the first 5 paragraphs of Source1.docx and the first
+ // five paragraphs of Source2.docx. This example returns a new WmlDocument, when you then can
+ // serialize to a SharePoint document library, or use in some other interesting scenario.
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument(source1), 0, 5, false),
+ new Source(new WmlDocument(source2), 0, 5, true),
+ };
+ WmlDocument out5 = DocumentBuilder.BuildDocument(sources);
+ out5.SaveAs(Path.Combine(tempDi.FullName, "Out5.docx")); // save it to the file system, but we could just as easily done something
+ // else with it.
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder01/DocumentBuilder01.csproj b/OpenXmlPowerToolsExamples/DocumentBuilder01/DocumentBuilder01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder01/DocumentBuilder01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/DocumentBuilder01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder01/Source1.docx b/OpenXmlPowerToolsExamples/DocumentBuilder01/Source1.docx
new file mode 100644
index 0000000..9e206b5
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder01/Source1.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder01/Source2.docx b/OpenXmlPowerToolsExamples/DocumentBuilder01/Source2.docx
new file mode 100644
index 0000000..1176fe0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder01/Source2.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder01/Source3.docx b/OpenXmlPowerToolsExamples/DocumentBuilder01/Source3.docx
new file mode 100644
index 0000000..4011449
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder01/Source3.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/Abstract.docx b/OpenXmlPowerToolsExamples/DocumentBuilder02/Abstract.docx
new file mode 100644
index 0000000..11dbf1a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/Abstract.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/AuthorBiography.docx b/OpenXmlPowerToolsExamples/DocumentBuilder02/AuthorBiography.docx
new file mode 100644
index 0000000..a2b4aa5
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/AuthorBiography.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/DocumentBuilder02.cs b/OpenXmlPowerToolsExamples/DocumentBuilder02/DocumentBuilder02.cs
new file mode 100644
index 0000000..d929a39
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/DocumentBuilder02.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+// Insert content into a document
+// Delete content from a document
+// Shred a document
+// Assemble it again, insert TOC
+
+class DocumentBuilderExample02
+{
+ private class DocumentInfo
+ {
+ public int DocumentNumber;
+ public int Start;
+ public int Count;
+ }
+
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ // Insert an abstract and author biography into a white paper.
+ List<Source> sources = null;
+
+ sources = new List<Source>()
+ {
+ new Source(new WmlDocument("../../WhitePaper.docx"), 0, 1, true),
+ new Source(new WmlDocument("../../Abstract.docx"), false),
+ new Source(new WmlDocument("../../AuthorBiography.docx"), false),
+ new Source(new WmlDocument("../../WhitePaper.docx"), 1, false),
+ };
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "AssembledPaper.docx"));
+
+ // Delete all paragraphs with a specific style.
+ using (WordprocessingDocument doc =
+ WordprocessingDocument.Open("../../Notes.docx", false))
+ {
+ sources = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Select((p, i) => new
+ {
+ Paragraph = p,
+ Index = i,
+ })
+ .GroupAdjacent(pi => (string)pi.Paragraph
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault() != "Note")
+ .Where(g => g.Key == true)
+ .Select(g => new Source(
+ new WmlDocument("../../Notes.docx"), g.First().Index,
+ g.Last().Index - g.First().Index + 1, true))
+ .ToList();
+ }
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "NewNotes.docx"));
+
+ // Shred a document into multiple parts for each section
+ List<DocumentInfo> documentList;
+ using (WordprocessingDocument doc =
+ WordprocessingDocument.Open("../../Spec.docx", false))
+ {
+ var sectionCounts = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Rollup(0, (pi, last) => (string)pi
+ .Elements(W.pPr)
+ .Elements(W.pStyle)
+ .Attributes(W.val)
+ .FirstOrDefault() == "Heading1" ? last + 1 : last);
+ var beforeZipped = doc
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements()
+ .Select((p, i) => new
+ {
+ Paragraph = p,
+ Index = i,
+ });
+ var zipped = PtExtensions.PtZip(beforeZipped, sectionCounts, (pi, sc) => new
+ {
+ Paragraph = pi.Paragraph,
+ Index = pi.Index,
+ SectionIndex = sc,
+ });
+ documentList = zipped
+ .GroupAdjacent(p => p.SectionIndex)
+ .Select(g => new DocumentInfo
+ {
+ DocumentNumber = g.Key,
+ Start = g.First().Index,
+ Count = g.Last().Index - g.First().Index + 1,
+ })
+ .ToList();
+ }
+ foreach (var doc in documentList)
+ {
+ string fileName = String.Format("Section{0:000}.docx", doc.DocumentNumber);
+ List<Source> documentSource = new List<Source> {
+ new Source(new WmlDocument("../../Spec.docx"), doc.Start, doc.Count, true)
+ };
+ DocumentBuilder.BuildDocument(documentSource, Path.Combine(tempDi.FullName, fileName));
+ }
+
+ // Re-assemble the parts into a single document.
+ sources = tempDi
+ .GetFiles("Section*.docx")
+ .Select(d => new Source(new WmlDocument(d.FullName), true))
+ .ToList();
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "ReassembledSpec.docx"));
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/DocumentBuilder02.csproj b/OpenXmlPowerToolsExamples/DocumentBuilder02/DocumentBuilder02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/DocumentBuilder02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/DocumentBuilder02/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/Notes.docx b/OpenXmlPowerToolsExamples/DocumentBuilder02/Notes.docx
new file mode 100644
index 0000000..0b5f791
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/Notes.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/Spec.docx b/OpenXmlPowerToolsExamples/DocumentBuilder02/Spec.docx
new file mode 100644
index 0000000..c040960
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/Spec.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder02/WhitePaper.docx b/OpenXmlPowerToolsExamples/DocumentBuilder02/WhitePaper.docx
new file mode 100644
index 0000000..9adbaaa
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder02/WhitePaper.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/DocumentBuilder03.cs b/OpenXmlPowerToolsExamples/DocumentBuilder03/DocumentBuilder03.cs
new file mode 100644
index 0000000..b8c176a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/DocumentBuilder03.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ WmlDocument doc1 = new WmlDocument(@"..\..\Template.docx");
+ using (MemoryStream mem = new MemoryStream())
+ {
+ mem.Write(doc1.DocumentByteArray, 0, doc1.DocumentByteArray.Length);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(mem, true))
+ {
+ XDocument xDoc = doc.MainDocumentPart.GetXDocument();
+ XElement frontMatterPara = xDoc.Root.Descendants(W.txbxContent).Elements(W.p).FirstOrDefault();
+ frontMatterPara.ReplaceWith(
+ new XElement(PtOpenXml.Insert,
+ new XAttribute("Id", "Front")));
+ XElement tbl = xDoc.Root.Element(W.body).Elements(W.tbl).FirstOrDefault();
+ XElement firstCell = tbl.Descendants(W.tr).First().Descendants(W.p).First();
+ firstCell.ReplaceWith(
+ new XElement(PtOpenXml.Insert,
+ new XAttribute("Id", "Liz")));
+ XElement secondCell = tbl.Descendants(W.tr).Skip(1).First().Descendants(W.p).First();
+ secondCell.ReplaceWith(
+ new XElement(PtOpenXml.Insert,
+ new XAttribute("Id", "Eric")));
+ doc.MainDocumentPart.PutXDocument();
+ }
+ doc1.DocumentByteArray = mem.ToArray();
+ }
+
+ string outFileName = Path.Combine(tempDi.FullName, "Out.docx");
+ List<Source> sources = new List<Source>()
+ {
+ new Source(doc1, true),
+ new Source(new WmlDocument(@"..\..\Insert-01.docx"), "Liz"),
+ new Source(new WmlDocument(@"..\..\Insert-02.docx"), "Eric"),
+ new Source(new WmlDocument(@"..\..\FrontMatter.docx"), "Front"),
+ };
+ DocumentBuilder.BuildDocument(sources, outFileName);
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/DocumentBuilder03.csproj b/OpenXmlPowerToolsExamples/DocumentBuilder03/DocumentBuilder03.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/DocumentBuilder03.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/FrontMatter.docx b/OpenXmlPowerToolsExamples/DocumentBuilder03/FrontMatter.docx
new file mode 100644
index 0000000..ad55bca
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/FrontMatter.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/Insert-01.docx b/OpenXmlPowerToolsExamples/DocumentBuilder03/Insert-01.docx
new file mode 100644
index 0000000..dc94837
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/Insert-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/Insert-02.docx b/OpenXmlPowerToolsExamples/DocumentBuilder03/Insert-02.docx
new file mode 100644
index 0000000..09646fb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/Insert-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/DocumentBuilder03/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder03/Template.docx b/OpenXmlPowerToolsExamples/DocumentBuilder03/Template.docx
new file mode 100644
index 0000000..1d5d02b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder03/Template.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/DocumentBuilder04.cs b/OpenXmlPowerToolsExamples/DocumentBuilder04/DocumentBuilder04.cs
new file mode 100644
index 0000000..20285e0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/DocumentBuilder04.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace ExampleDocumentBuilder04
+{
+ class ContentControlsExample
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ WmlDocument solarSystemDoc = new WmlDocument("../../solar-system.docx");
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(solarSystemDoc))
+ using (WordprocessingDocument solarSystem = streamDoc.GetWordprocessingDocument())
+ {
+ // get children elements of the <w:body> element
+ var q1 = solarSystem
+ .MainDocumentPart
+ .GetXDocument()
+ .Root
+ .Element(W.body)
+ .Elements();
+
+ // project collection of tuples containing element and type
+ var q2 = q1
+ .Select(
+ e =>
+ {
+ string keyForGroupAdjacent = ".NonContentControl";
+ if (e.Name == W.sdt)
+ keyForGroupAdjacent = e.Element(W.sdtPr)
+ .Element(W.tag)
+ .Attribute(W.val)
+ .Value;
+ if (e.Name == W.sectPr)
+ keyForGroupAdjacent = null;
+ return new
+ {
+ Element = e,
+ KeyForGroupAdjacent = keyForGroupAdjacent
+ };
+ }
+ ).Where(e => e.KeyForGroupAdjacent != null);
+
+ // group by type
+ var q3 = q2.GroupAdjacent(e => e.KeyForGroupAdjacent);
+
+ // temporary code to dump q3
+ foreach (var g in q3)
+ Console.WriteLine("{0}: {1}", g.Key, g.Count());
+ //Environment.Exit(0);
+
+
+ // validate existence of files referenced in content controls
+ foreach (var f in q3.Where(g => g.Key != ".NonContentControl"))
+ {
+ string filename = "../../" + f.Key + ".docx";
+ FileInfo fi = new FileInfo(filename);
+ if (!fi.Exists)
+ {
+ Console.WriteLine("{0} doesn't exist.", filename);
+ Environment.Exit(0);
+ }
+ }
+
+ // project collection with opened WordProcessingDocument
+ var q4 = q3
+ .Select(g => new
+ {
+ Group = g,
+ Document = g.Key != ".NonContentControl" ?
+ new WmlDocument("../../" + g.Key + ".docx") :
+ solarSystemDoc
+ });
+
+ // project collection of OpenXml.PowerTools.Source
+ var sources = q4
+ .Select(
+ g =>
+ {
+ if (g.Group.Key == ".NonContentControl")
+ return new Source(
+ g.Document,
+ g.Group
+ .First()
+ .Element
+ .ElementsBeforeSelf()
+ .Count(),
+ g.Group
+ .Count(),
+ false);
+ else
+ return new Source(g.Document, false);
+ }
+ ).ToList();
+
+ DocumentBuilder.BuildDocument(sources, Path.Combine(tempDi.FullName, "solar-system-new.docx"));
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/DocumentBuilder04.csproj b/OpenXmlPowerToolsExamples/DocumentBuilder04/DocumentBuilder04.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/DocumentBuilder04.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Earth.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Earth.docx
new file mode 100644
index 0000000..d28f47a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Earth.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Jupiter.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Jupiter.docx
new file mode 100644
index 0000000..8eee311
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Jupiter.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Mars.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Mars.docx
new file mode 100644
index 0000000..290ba36
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Mars.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Mercury.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Mercury.docx
new file mode 100644
index 0000000..d46f228
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Mercury.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Neptune.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Neptune.docx
new file mode 100644
index 0000000..04a196e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Neptune.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/DocumentBuilder04/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Pluto.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Pluto.docx
new file mode 100644
index 0000000..cce9987
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Pluto.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Saturn.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Saturn.docx
new file mode 100644
index 0000000..5fb8c37
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Saturn.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/SolarOverview.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/SolarOverview.docx
new file mode 100644
index 0000000..4b093d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/SolarOverview.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Sun.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Sun.docx
new file mode 100644
index 0000000..e17c404
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Sun.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Uranus.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Uranus.docx
new file mode 100644
index 0000000..d1c0a37
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Uranus.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/Venus.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/Venus.docx
new file mode 100644
index 0000000..e89f5fa
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/Venus.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/DocumentBuilder04/solar-system.docx b/OpenXmlPowerToolsExamples/DocumentBuilder04/solar-system.docx
new file mode 100644
index 0000000..578bb08
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/DocumentBuilder04/solar-system.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/FieldRetriever01/DocWithFooter1.docx b/OpenXmlPowerToolsExamples/FieldRetriever01/DocWithFooter1.docx
new file mode 100644
index 0000000..1606c23
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FieldRetriever01/DocWithFooter1.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/FieldRetriever01/DocWithFooter2.docx b/OpenXmlPowerToolsExamples/FieldRetriever01/DocWithFooter2.docx
new file mode 100644
index 0000000..b05975e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FieldRetriever01/DocWithFooter2.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/FieldRetriever01/FieldRetriever01.cs b/OpenXmlPowerToolsExamples/FieldRetriever01/FieldRetriever01.cs
new file mode 100644
index 0000000..55a2a96
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FieldRetriever01/FieldRetriever01.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class FieldRetriever01
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ var docWithFooter = new FileInfo("../../DocWithFooter1.docx");
+ var scrubbedDocument = new FileInfo(Path.Combine(tempDi.FullName, "DocWithFooterScrubbed1.docx"));
+ File.Copy(docWithFooter.FullName, scrubbedDocument.FullName);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(scrubbedDocument.FullName, true))
+ {
+ ScrubFooter(wDoc, new [] { "PAGE" });
+ }
+
+ docWithFooter = new FileInfo("../../DocWithFooter2.docx");
+ scrubbedDocument = new FileInfo(Path.Combine(tempDi.FullName, "DocWithFooterScrubbed2.docx"));
+ File.Copy(docWithFooter.FullName, scrubbedDocument.FullName);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(scrubbedDocument.FullName, true))
+ {
+ ScrubFooter(wDoc, new[] { "PAGE", "DATE" });
+ }
+ }
+
+ private static void ScrubFooter(WordprocessingDocument wDoc, string[] fieldTypesToKeep)
+ {
+ foreach (var footer in wDoc.MainDocumentPart.FooterParts)
+ {
+ FieldRetriever.AnnotateWithFieldInfo(footer);
+ XElement root = footer.GetXDocument().Root;
+ RemoveAllButSpecificFields(root, fieldTypesToKeep);
+ footer.PutXDocument();
+ }
+ }
+
+ private static void RemoveAllButSpecificFields(XElement root, string[] fieldTypesToRetain)
+ {
+ var cachedAnnotationInformation = root.Annotation<Dictionary<int, List<XElement>>>();
+ List<XElement> runsToKeep = new List<XElement>();
+ foreach (var item in cachedAnnotationInformation)
+ {
+ var runsForField = root
+ .Descendants()
+ .Where(d =>
+ {
+ Stack<FieldRetriever.FieldElementTypeInfo> stack = d.Annotation<Stack<FieldRetriever.FieldElementTypeInfo>>();
+ if (stack == null)
+ return false;
+ if (stack.Any(stackItem => stackItem.Id == item.Key))
+ return true;
+ return false;
+ })
+ .Select(d => d.AncestorsAndSelf(W.r).FirstOrDefault())
+ .GroupAdjacent(o => o)
+ .Select(g => g.First())
+ .ToList();
+ foreach (var r in runsForField)
+ runsToKeep.Add(r);
+ }
+ foreach (var paragraph in root.Descendants(W.p).ToList())
+ {
+ if (paragraph.Elements(W.r).Any(r => runsToKeep.Contains(r)))
+ {
+ paragraph.Elements(W.r)
+ .Where(r => !runsToKeep.Contains(r) &&
+ !r.Elements(W.tab).Any())
+ .Remove();
+ paragraph.Elements(W.r)
+ .Where(r => !runsToKeep.Contains(r))
+ .Elements()
+ .Where(rc => rc.Name != W.rPr &&
+ rc.Name != W.tab)
+ .Remove();
+ }
+ else
+ {
+ paragraph.Remove();
+ }
+ }
+ root.Descendants(W.tbl).Remove();
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/FieldRetriever01/FieldRetriever01.csproj b/OpenXmlPowerToolsExamples/FieldRetriever01/FieldRetriever01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FieldRetriever01/FieldRetriever01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/FieldRetriever01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/FieldRetriever01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FieldRetriever01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/FormattingAssembler01/FormattingAssembler01.cs b/OpenXmlPowerToolsExamples/FormattingAssembler01/FormattingAssembler01.cs
new file mode 100644
index 0000000..55925b1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FormattingAssembler01/FormattingAssembler01.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace FormattingAssembler01
+{
+ class FormattingAssembler01
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ DirectoryInfo di = new DirectoryInfo("../../");
+ foreach (var file in di.GetFiles("*.docx"))
+ {
+ Console.WriteLine(file.Name);
+ var newFile = new FileInfo(Path.Combine(tempDi.FullName, file.Name.Replace(".docx", "out.docx")));
+ File.Copy(file.FullName, newFile.FullName);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(newFile.FullName, true))
+ {
+ FormattingAssemblerSettings settings = new FormattingAssemblerSettings()
+ {
+ ClearStyles = true,
+ RemoveStyleNamesFromParagraphAndRunProperties = true,
+ CreateHtmlConverterAnnotationAttributes = true,
+ OrderElementsPerStandard = true,
+ RestrictToSupportedLanguages = true,
+ RestrictToSupportedNumberingFormats = true,
+ };
+ FormattingAssembler.AssembleFormatting(wDoc, settings);
+ }
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/FormattingAssembler01/FormattingAssembler01.csproj b/OpenXmlPowerToolsExamples/FormattingAssembler01/FormattingAssembler01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FormattingAssembler01/FormattingAssembler01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/FormattingAssembler01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/FormattingAssembler01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FormattingAssembler01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/FormattingAssembler01/Test01.docx b/OpenXmlPowerToolsExamples/FormattingAssembler01/Test01.docx
new file mode 100644
index 0000000..dde4dd1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FormattingAssembler01/Test01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/FormattingAssembler01/Test02.docx b/OpenXmlPowerToolsExamples/FormattingAssembler01/Test02.docx
new file mode 100644
index 0000000..89b33cd
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FormattingAssembler01/Test02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/FormattingAssembler01/Test03.docx b/OpenXmlPowerToolsExamples/FormattingAssembler01/Test03.docx
new file mode 100644
index 0000000..0445249
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/FormattingAssembler01/Test03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/Formulas01/Formulas.xlsx b/OpenXmlPowerToolsExamples/Formulas01/Formulas.xlsx
new file mode 100644
index 0000000..5135d9f
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/Formulas01/Formulas.xlsx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/Formulas01/Formulas01.cs b/OpenXmlPowerToolsExamples/Formulas01/Formulas01.cs
new file mode 100644
index 0000000..24e5903
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/Formulas01/Formulas01.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using ExcelFormula;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace ExampleFormulas
+{
+ class ExampleFormulas
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ // Change sheet name in formulas
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(
+ SmlDocument.FromFileName("../../Formulas.xlsx")))
+ {
+ using (SpreadsheetDocument doc = streamDoc.GetSpreadsheetDocument())
+ {
+ WorksheetAccessor.FormulaReplaceSheetName(doc, "Source", "'Source 2'");
+ }
+ streamDoc.GetModifiedSmlDocument().SaveAs(Path.Combine(tempDi.FullName, "FormulasUpdated.xlsx"));
+ }
+
+ // Change sheet name in formulas
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(
+ SmlDocument.FromFileName("../../Formulas.xlsx")))
+ {
+ using (SpreadsheetDocument doc = streamDoc.GetSpreadsheetDocument())
+ {
+ WorksheetPart sheet = WorksheetAccessor.GetWorksheet(doc, "References");
+ WorksheetAccessor.CopyCellRange(doc, sheet, 1, 1, 7, 5, 4, 8);
+ }
+ streamDoc.GetModifiedSmlDocument().SaveAs(Path.Combine(tempDi.FullName, "FormulasCopied.xlsx"));
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/Formulas01/Formulas01.csproj b/OpenXmlPowerToolsExamples/Formulas01/Formulas01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/Formulas01/Formulas01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/Formulas01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/Formulas01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/Formulas01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/5DayTourPlanTemplate.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/5DayTourPlanTemplate.docx
new file mode 100644
index 0000000..394766a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/5DayTourPlanTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Contract.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Contract.docx
new file mode 100644
index 0000000..99a8965
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Contract.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Hebrew-01.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Hebrew-01.docx
new file mode 100644
index 0000000..9089200
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Hebrew-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Hebrew-02.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Hebrew-02.docx
new file mode 100644
index 0000000..2b80d67
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Hebrew-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/HtmlConverter01.cs b/OpenXmlPowerToolsExamples/HtmlConverter01/HtmlConverter01.cs
new file mode 100644
index 0000000..52777e8
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/HtmlConverter01.cs
@@ -0,0 +1,167 @@
+/***************************************************************************
+
+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
+
+***************************************************************************/
+
+/***************************************************************************
+ * IMPORTANT NOTE:
+ *
+ * With versions 4.1 and later, the name of the HtmlConverter class has been
+ * changed to WmlToHtmlConverter, to make it orthogonal with HtmlToWmlConverter.
+ *
+ * There are thin wrapper classes, HtmlConverter, and HtmlConverterSettings,
+ * which maintain backwards compat for code that uses the old name.
+ *
+ * Other than the name change of the classes themselves, the functionality
+ * in WmlToHtmlConverter is identical to the old HtmlConverter class.
+***************************************************************************/
+
+using System;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class HtmlConverterHelper
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ /*
+ * This example loads each document into a byte array, then into a memory stream, so that the document can be opened for writing without
+ * modifying the source document.
+ */
+ foreach (var file in Directory.GetFiles("../../", "*.docx"))
+ {
+ ConvertToHtml(file, tempDi.FullName);
+ }
+ }
+
+ public static void ConvertToHtml(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ Console.WriteLine(fi.Name);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+
+ var pageTitle = fi.FullName;
+ var part = wDoc.CoreFilePropertiesPart;
+ if (part != null)
+ {
+ pageTitle = (string) part.GetXDocument().Descendants(DC.title).FirstOrDefault() ?? fi.FullName;
+ }
+
+ // TODO: Determine max-width from size of content area.
+ HtmlConverterSettings settings = new HtmlConverterSettings()
+ {
+ AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ imageFormat = ImageFormat.Png;
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ string imageSource = localDirInfo.Name + "/image" +
+ imageCounter.ToString() + "." + extension;
+
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageSource),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement htmlElement = HtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Produce HTML document with <!DOCTYPE html > declaration to tell the browser
+ // we are using HTML5.
+ var html = new XDocument(
+ new XDocumentType("html", null, null, null),
+ htmlElement);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/HtmlConverter01.csproj b/OpenXmlPowerToolsExamples/HtmlConverter01/HtmlConverter01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/HtmlConverter01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/HtmlConverter01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/ResumeTemplate.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/ResumeTemplate.docx
new file mode 100644
index 0000000..c1e50fe
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/ResumeTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/TaskPlanTemplate.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/TaskPlanTemplate.docx
new file mode 100644
index 0000000..f5932f6
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/TaskPlanTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-01.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-01.docx
new file mode 100644
index 0000000..46ed41a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-02.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-02.docx
new file mode 100644
index 0000000..f6628ca
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-03.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-03.docx
new file mode 100644
index 0000000..676c8cb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-04.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-04.docx
new file mode 100644
index 0000000..797d694
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-05.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-05.docx
new file mode 100644
index 0000000..7df6284
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-06.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-06.docx
new file mode 100644
index 0000000..2d56602
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-07.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-07.docx
new file mode 100644
index 0000000..8971061
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlConverter01/Test-08.docx b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-08.docx
new file mode 100644
index 0000000..0e21abe
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlConverter01/Test-08.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate.html
new file mode 100644
index 0000000..79d85c3
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Title {
+ margin-bottom: 26pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 33pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #FA5A00;
+ font-family: Microsoft YaHei UI;
+ font-size: 33pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ margin-bottom: .001pt;
+}
+td.pt-000001 {
+ vertical-align: top;
+ width: 53.85pt;
+ border-top: none;
+ border-right: solid #DADADA 2.3pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+h1.pt-Heading1 {
+ line-height: 90.0%;
+ font-family: Microsoft YaHei UI;
+ font-size: 26pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000002 {
+ color: #FA5A00;
+ font-family: Microsoft YaHei UI;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000003 {
+ vertical-align: top;
+ width: 320.45pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #DADADA 2.3pt;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal {
+ font-family: Microsoft YaHei UI;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000004 {
+ color: #404040;
+ font-family: Microsoft YaHei UI;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 133.15pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-DefaultParagraphFont-000006 {
+ color: #404040;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000007 {
+ height: 0.44in;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 53.85pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-000009 {
+ color: #FA5A00;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: top;
+ width: 320.45pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-000011 {
+ color: #404040;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-Title"><span lang="zh-CN" class="pt-DefaultParagraphFont">5 日游行程计划</span></p><div align="left"><table dir="ltr" class="pt-000000"><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">1 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">目的地</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:[</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">您要去哪儿</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">在哪里吃饭:[早餐吃什么?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">安排什么活动:[是否要买演出的票?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">待在哪里</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:[沙滩屋还是朋友的沙发?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">抵达方式:[飞机、火车还是 GPS 自驾?]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="5DayTourPlanTemplate_files/image1.jpeg" style="width: 1.849306in; height: 1.249306in" alt="图片 4" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">2 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">[要替换任何占位符文本(例如此文本),只需选中一行或一段文本并开始键入。为达到最佳效果,请勿在您选中的字符左侧或右侧包含空格。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="5DayTourPlanTemplate_files/image2.jpeg" style="width: 1.849306in; height: 1.249306in" alt="图片 5" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">3 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">[要将任何占位符照片替换为您自己的照片,请删除它,然后在功能区的“插入”选项卡上单击“图片”。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="5DayTourPlanTemplate_files/image3.jpeg" style="width: 1.849306in; height: 1.249306in" alt="图片 6" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">4 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">目的地:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">在哪里吃饭:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">安排什么活动:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">待在哪里</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">抵达方式:</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="5DayTourPlanTemplate_files/image4.jpeg" style="width: 1.849306in; height: 1.249306in" alt="图片 7" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">5 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">目的地:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">在哪里吃饭:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">安排什么活动:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">待在哪里</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">抵达方式:</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="5DayTourPlanTemplate_files/image5.jpeg" style="width: 1.849306in; height: 1.249306in" alt="图片 8" /></span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image1.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image1.jpeg
new file mode 100644
index 0000000..4e1b28f
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image1.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image2.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image2.jpeg
new file mode 100644
index 0000000..a6d2ec1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image2.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image3.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image3.jpeg
new file mode 100644
index 0000000..9a11f3e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image3.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image4.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image4.jpeg
new file mode 100644
index 0000000..19d448b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image4.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image5.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image5.jpeg
new file mode 100644
index 0000000..69dfbeb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/5DayTourPlanTemplate_files/image5.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Contract.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Contract.html
new file mode 100644
index 0000000..4d6265b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Contract.html
@@ -0,0 +1,174 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title>C:\Users\Eric\Documents\Open-Xml-PowerTools\OpenXmlPowerToolsExamples\HtmlConverter01\Contract.docx</title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-000000 {
+ margin-top: 12pt;
+ margin-bottom: 12pt;
+ margin-left: 0.50in;
+ text-indent: -0.50in;
+ font-family: Times New Roman Bold;
+ font-size: 14pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-000001 {
+ font-family: Times New Roman Bold;
+ font-size: 14pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.500in;
+}
+a.pt-000002 {
+ text-decoration: none;
+}
+span.pt-DefaultParagraphFont {
+ font-family: Times New Roman Bold;
+ font-size: 14pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000003 {
+ margin-top: 6pt;
+ margin-bottom: 0;
+ margin-left: 0.50in;
+ text-indent: -0.35in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-000004 {
+ color: black;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.350in;
+}
+span.pt-DefaultParagraphFont-000005 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000006 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000007 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000008 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ text-decoration: underline;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000009 {
+ margin-top: 6pt;
+ margin-bottom: 6pt;
+ margin-left: 0.50in;
+ text-indent: -0.35in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+p.pt-TableText {
+ margin-top: 6pt;
+ margin-bottom: 6pt;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000010 {
+ font-family: Times New Roman Bold;
+ font-size: 14pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.500in;
+}
+span.pt-DefaultParagraphFont-000011 {
+ font-family: Times New Roman Bold;
+ font-size: 14pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000012 {
+ color: #000000;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ letter-spacing: 0;
+ position: relative;
+ top: 0pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.350in;
+}
+p.pt-TableLevel3Numbered {
+ margin-top: 6pt;
+ margin-bottom: 6pt;
+ margin-left: 0.49in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+p.pt-000013 {
+ margin-top: 6pt;
+ margin-bottom: 0;
+ margin-left: 0.90in;
+ text-indent: -0.75in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-000014 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.750in;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-000000"><span class="pt-000001">1.0</span><a id="_Toc357585812" class="pt-000002"></a><a id="_Toc351296220" class="pt-000002"></a><span lang="de-DE" class="pt-DefaultParagraphFont">EINLEITUNG</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">1.</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005">Der IT-DIENSTLEISTER bestätigt, dass er – falls nicht anders angegeben – eine </span><span lang="de-DE" class="pt-DefaultParagraphFont-000006">Lösung</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005"> bereitstellen wird, die alle in dieser LEISTUNGSBESCHREIBUNG (Statement of Work - SOW) und ihren Anhängen angeführten </span><span lang="de-DE" class="pt-DefaultParagraphFont-000007">Geschäftsprozesse</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005"> unterstützt und dass alle SERVICES - falls nicht ausdrücklich anderweitig festgelegt - in den BASE CHARGES enthalten sind. Der IT-DIENSTLEISTER verpflichtet sich zu dem Ansatz einer kontinuierlichen </span><span lang="de-DE" class="pt-DefaultParagraphFont-000008">Verbesserung</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">.</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">2.</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005">Der IT-DIENSTLEISTER reagiert auf derzeitige und künftige Anforderungen von KUNDE, schätzt proaktiv den künftigen Bedarf und wird seine Dienstleistungen innerhalb der BASE CHARGES entsprechend adjustieren. Anforderungen bezüglich neuer Dienstleistungen </span><span lang="de-AT" class="pt-DefaultParagraphFont-000005">oder gravierender Änderung der Rahmenbedingungen</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005"> werden innerhalb des CHANGE CONTROL VERFAHRENS behandelt, wobei der IT-DIENSTLEISTER in Zusammenarbeit mit KUNDE die Auswirkungen einer solchen Anforderung auf die Betriebsumgebung von KUNDE beurteilt.</span></p><p dir="ltr" class="pt-000009"><span lang="de-DE" class="pt-000004">3.</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005">Ab dem ANFANGSDATUM wird der IT-DIENSTLEISTER für die Ausführung der in dieser LEISTUNGSBESCHREIBUNG festgelegten Application Development und Maintenance Service (ADM SERVICES) verantwortlich sein. Der IT-DIENSTLEISTER </span><span lang="de-AT" class="pt-DefaultParagraphFont-000005">wird den in Anhang X festgelegten Umfang der ADM Services für die ANWENDUNGSSOFTWARE erbringen.</span></p><p dir="ltr" class="pt-000000"><span lang="de-DE" class="pt-000001">2.0</span><span lang="de-DE" class="pt-DefaultParagraphFont">Beispiel-Überschrift</span></p><p dir="ltr" class="pt-TableText"><span lang="de-DE" class="pt-DefaultParagraphFont-000005">KUNDE pflegt und verwendet mehrere Entwicklungs-Methoden, die grundsätzlich bei der Leistungserbringung zu berücksichtigen sind. Der IT-DIENSTLEISTER hat diese Methoden zu verwenden, kann aber bei berechtigten Gründen eine alternative Methode vorschlagen, die dann von KUNDE geprüft und genehmigt werden muss.</span></p><p dir="ltr" class="pt-000000"><span lang="de-DE" class="pt-000010">2.1</span><a id="_Toc357585814" class="pt-000002"></a><a id="_Toc14055702" class="pt-000002"></a><a id="_Toc177292374" class="pt-000002"></a><a id="_Toc255104734" class="pt-000002"></a><a id="_Toc351296222" class="pt-000002"></a><span lang="de-DE" class="pt-DefaultParagraphFont-000011">Methodik, Werkzeuge und Verfahren</span></p><p dir="ltr" class="pt-TableText"><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Zu den Aufgaben des IT-DIENSTLEISTERS gehören unter Einhaltung der KUNDE Standards:</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">1.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Dokumentation und Verfeinerung der ANWENDUNGS-Entwicklungsmethoden</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">2.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Weiterentwicklung von Methoden, Abläufen und Verfahren für die Erbringung von Services in der ANWENDUNGS-Entwicklungsmethoden und -betrieb.</span></p><p dir="ltr" class="pt-000009"><span lang="de-DE" class="pt-000004">3.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Koordination der Implementierung von Methoden, Abläufen und Verfahren.</span></p><p dir="ltr" class="pt-000000"><span class="pt-000010">2.2</span><a id="_Toc357585815" class="pt-000002"></a><a id="_Toc14055703" class="pt-000002"></a><a id="_Toc177292375" class="pt-000002"></a><a id="_Toc255104735" class="pt-000002"></a><a id="_Toc351296223" class="pt-000002"></a><span lang="de-DE" class="pt-DefaultParagraphFont-000011">Anwendungsstandards</span></p><p dir="ltr" class="pt-TableText"><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Zu den Aufgaben des IT-DIENSTLEISTERS gehören:</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">1.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Einhaltung der Standards für Benutzeroberfläche, Maschinenschnittstelle und Programmierung von KUNDE (z.B. GUI, EDI und IP) bei allen Entwicklungs-, Erweiterungs- und Wartungsaktivitäten. Aufbauend auf früheren Erfahrungen kann der IT-DIENSTLEISTER Verbesserungen an den ANWENDUNGS-Standards vorschlagen, und im Falle einer Annahme und Genehmigung dieser Vorschläge durch KUNDE, die ANWENDUNGS-Standards überarbeiten.</span></p><p dir="ltr" class="pt-000009"><span lang="de-DE" class="pt-000004">2.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Entwicklung und Kommunikation dieser ANWENDUNGS-Standards.</span></p><p dir="ltr" class="pt-000000"><span class="pt-000010">2.3</span><a id="_Toc357585832" class="pt-000002"></a><a id="_Toc14055719" class="pt-000002"></a><a id="_Toc177292389" class="pt-000002"></a><a id="_Toc255104749" class="pt-000002"></a><a id="_Toc351296240" class="pt-000002"></a><span lang="de-DE" class="pt-DefaultParagraphFont-000011">Fehlerkorrektur</span></p><p dir="ltr" class="pt-TableText"><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Zu den Aufgaben des IT-DIENSTLEISTERS gehören:</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">1.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Behebung aller ANWENDUNGS-Incidents bzw. -Probleme, die Änderungen der Datenbank, des ANWENDUNGS-Codes und/oder des Betriebsablaufs erfordern, die aus der Fehlerkorrektur resultieren.</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">2.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Übernahme der Zuständigkeit für vom IT-DIENSTLEISTER gewartete SOFTWARE, THIRD PARTY SOFTWARE von KUNDE und THIRD PARTY SOFTWARE des IT-DIENSTLEISTERS durch folgende Maßnahmen in Abstimmung und Freigabe durch KUNDE:</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000012">2.1.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Erkennung von ANWENDUNGS- und/oder Datenbankproblemen.</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000012">2.2.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Benachrichtigung des zuständigen IT-DIENSTLEISTERS.</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000012">2.3.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Veranlassung der durchzuführenden Korrekturen.</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">3.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Bereitstellung eines Notfallsupports mit folgenden Zielen:</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000012">3.1.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Vermeidung abnormaler Programmausfälle in der Produktionsumgebung.</span></p><p dir="ltr" class="pt-000009"><span lang="de-DE" class="pt-000012">3.2.</span><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005">Die Behebung aller anderen Probleme, die in Verbindung mit einer ANWENDUNG, Datenbank und, falls zutreffend, SYSTEM SOFTWARE auftreten können („Korrektur im Fehlerfall“). </span></p><p dir="ltr" class="pt-TableLevel3Numbered"><span lang="de-DE" xml:space="preserve" class="pt-DefaultParagraphFont-000005">Es müssen Workarounds/Zwischenlösungen geschaffen bzw. implementiert werden, zur Eingrenzung möglicher Business Beeinträchtigungen. </span></p><p dir="ltr" class="pt-TableLevel3Numbered"><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Dies schließt alle Aktionen ein, die erforderlich sind, um sowohl die ANWENDUNG als auch alle SERVICES für KUNDE wiederherzustellen, einschließlich der Koordination der Betriebsabläufe mit den Betriebsverantwortlichen des ANWENDUNGS- und IT-Infrastrukturbetriebs, um Produktionspläne in folgenden Fällen neu aufzusetzen oder zu ergänzen:</span></p><p dir="ltr" class="pt-000013"><span lang="de-DE" class="pt-000014">3.2.1.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die eingeschränkte VERFÜGBARKEIT von kritischen Schnittstellen bzw. ANWENDUNGEN, Datenbanken oder SYSTEM SOFTWARE.</span></p><p dir="ltr" class="pt-000013"><span lang="de-DE" class="pt-000014">3.2.2.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Probleme mit EQUIPMENT oder NETZWERK Kommunikation.</span></p><p dir="ltr" class="pt-000003"><span lang="de-DE" class="pt-000004">4.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die enge Zusammenarbeit mit zuständigen MITARBEITERN von KUNDE, um eine angemessene Berichterstattung über Fortschritte und eine effektive Lösung von Produktionsproblemen sicherzustellen.</span></p><p dir="ltr" class="pt-000009"><span lang="de-DE" class="pt-000004">5.</span><span lang="de-DE" class="pt-DefaultParagraphFont-000005">Die Durchführung von Aktivitäten zur Fehlerkorrektur in Übereinstimmung mit SERVICE LEVEL-Anforderungen.</span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01.html
new file mode 100644
index 0000000..b3bb7e5
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title> </title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ text-align: center;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000000 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000001 {
+ text-align: center;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000002 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000003 {
+ text-align: center;
+ font-family: David;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000004 {
+ font-family: David;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000005 {
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000006 {
+ font-family: David;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000007 {
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.500in;
+}
+span.pt-000008 {
+ margin: 0 0 0 0.50in;
+ padding: 0 0 0 0;
+}
+p.pt-Normal-000009 {
+ text-align: center;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000010 {
+ color: #000000;
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000011 {
+ color: #000000;
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000012 {
+ color: #000000;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000013 {
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000014 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div dir="rtl"><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-DefaultParagraphFont"><img src="Hebrew-01_files/image1.png" style="width: 1.84375in; height: 2.427083in" alt="Picture 1" /></span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000001">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000002">‏טיול ליפן אפריל 2014‏</span></p><p dir="rtl" class="pt-Normal-000003">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏שלום רב,‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בחרתם לצאת ולתור את "ארץ השמש העולה", הלא היא יפן.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מרגע נחיתתכם באי הונשו ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ שהוא האי הגדול מבין ארבעת איי יפן העיקריים, תבינו שיפן היא לא רק "סוני", "טיוטה", "מיצובישי" וכו' אלא מדינה מרתקת, יפה ומגוונת.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ההתרגשות לקראת הנסיעה הולכת וגוברת עם התקרב מועד היציאה, ויחד עם זאת ישנן שאלות שעליהן הנכם מחכים לקבל תשובות.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מקווה שדף מידע זה יהיה לכם לעזר. אם לאחר מפגש הקבוצה וקריאת המידע המובא כאן תתעוררנה שאלות, הרגישו חופשיים ליצור עמי קשר עוד בטרם היציאה לטיול. אשתדל לתת לכם את האינפורמציה הנדרשת.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏להלן מספרי הטלפון שלי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בית ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 09-7425733‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏נייד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 052-2231525 ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מזג אויר ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ אביבי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ (כדאי ורצוי לבדוק באינטרנט לפני היציאה לטיול)‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מטבע מקומי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ יין (‏</span><span class="pt-DefaultParagraphFont-000002">‎JPY‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ )‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏שער חליפין ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000007">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏1$= 102.3 יין‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span class="pt-DefaultParagraphFont"><span class="pt-000008"> </span></span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏1ש"ח=29.41 יין‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏(שער ההמרה נכון ל ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 9/4/2014) ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏הוצאה יומית לאדם ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ כ- 25$‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בבתי עסק ובבתי המלון ניתן להשתמש בכרטיסי אשראי.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏הפרשי השעות בין ישראל ליפן ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 6+ (יפן מקדימה את ישראל ב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 6 שעות).‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תקשורת ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ הקידומת ליפן היא 81 ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏טלפון מיפן לארץ: קוד גישה בינלאומי; ‏</span><span class="pt-DefaultParagraphFont-000002">‎JDC 001;KDD 0041;JT 0061 ‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ +972 +קידומת האזור בלי "0"'‏</span><span class="pt-DefaultParagraphFont-000002">‎ ‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏+מספר הטלפון.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏לבעלי סמארט פון ממליצה להוריד את התוכנה ‏</span><span class="pt-DefaultParagraphFont-000002">‎BPHON‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ של בזק. (רק בעלי קו בזק בביתם יכולים להוריד תוכנה זו).‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ביפן ניתן להשתמש רק בפלפונים מהדור השלישי ומעלה.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תקשורת אינטרנטית ברוב בתי המלון.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="ltr" class="pt-Normal-000009"><span lang="he-IL" class="pt-DefaultParagraphFont-000007">‏ ‏</span><span class="pt-DefaultParagraphFont-000010"><span class="pt-000008"> </span></span><span class="pt-DefaultParagraphFont-000010"><img src="Hebrew-01_files/image2.jpeg" style="width: 1.322917in; height: 0.4895833in" alt="ib2225" /></span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏חשמל ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏ הספק ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏ 100‏</span><span class="pt-DefaultParagraphFont-000012">‎V‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏ .כך נראה תקע יפאני ולו יש להתאים מעביר.‏</span><span class="pt-DefaultParagraphFont-000012">‎ ‎</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏לבוש וציוד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ בגדים מתאימים למזג האויר. נכון לימים אלה הטמפ' היא בסביבות 20+ מעלות במהלך היום.יש להביא מעיל ,צעיף,מטריה, נעלי הליכה נוחות, נעלים להחלפה.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏שימו לב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏ בביקור בהר קויה עשוי להיות קר יותר מאשר ביתר חלקי הטיול.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏קרם הגנה‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏משקפי קריאה רזרביים‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏משקפי שמש‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תרופות ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ לעזרה ראשונה, כמו קולדקס, סטופ איט, דקסמול ומעורר פעולת מעים.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏צילום של הדרכון ושל כרטיס הטיסה.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏באם יש לך ספור מעניין/מוסיקה טובה/ רעיון למשחק ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏ נחמד באם גם פריטים אלה יהיו בין הדברים שהנך מביא/ה לטיול.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מזוודה ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ מזוודת המטען לא תעלה על ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏20 ק"ג‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ לנוסע.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תיק יד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ (מזוודת יד) ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ כדאי ורצוי לארוז חליפת בגדים ובגדים תחתונים;תרופות לשימוש יום יומי.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בתיק היד ניתן להכניס מיכלים שהתכולה שלהם לא עולה על 50 מ"ל. אנא הקפידו על גודל זה.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏כמו כן יש להוציא כל מכשיר חד מתיק היד.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏מבקשת מכל חברי הקבוצה להגיע ביום רביעי ה- 16/4/2014 בשעה 09:30 לשער מס' 32 שבשדה התעופה. אודה לכולם באם תעמדו בלוח זמנים זה.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏רק לאחר שהודעתם לי על הגעתכם תתחילו בתהליך הצ'אק אין. את מזוודת המטען יש לשלח ישירות לטוקיו.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏עלינו לזכור שהצלחת הטיול תלויה בכל אחד מאתנו. אחד הכללים החשבוים הוא עמידה בלוח זמנים !!!!‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏ברצוני לאחל לכם ולב"ב חג אביב צבעוני ושמח !!!‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בברכה,‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏שרה פרי‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01_files/image1.png b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01_files/image1.png
new file mode 100644
index 0000000..90deeb0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01_files/image1.png
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01_files/image2.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01_files/image2.jpeg
new file mode 100644
index 0000000..cb5c63b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-01_files/image2.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-02.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-02.html
new file mode 100644
index 0000000..af21ae0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-02.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title> </title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ text-align: center;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000000 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000001 {
+ text-align: center;
+ font-family: Guttman Yad-Brush;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000002 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000003 {
+ text-align: center;
+ font-family: David;
+ font-size: 16pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000004 {
+ font-family: David;
+ font-size: 16pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000005 {
+ text-align: center;
+ font-family: David;
+ font-size: 16pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000006 {
+ font-size: 16pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000007 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000008 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000009 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000010 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000011 {
+ margin-right: 0.50in;
+ text-indent: -0.25in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000012 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-DefaultParagraphFont-000013 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000014 {
+ margin: 0 0 0 0.50in;
+ padding: 0 0 0 0;
+}
+p.pt-Normal-000015 {
+ margin-right: 0.02in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000016 {
+ margin-right: 0.02in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000017 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-000018 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-DefaultParagraphFont-000019 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000020 {
+ margin-right: 0.25in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000021 {
+ margin-left: 0.02in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000022 {
+ margin-left: 0.02in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div dir="rtl"><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‎ ‎</span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-DefaultParagraphFont"><img src="Hebrew-02_files/image1.gif" style="width: 2.15625in; height: 2.375in" alt="MMj02831010000[1]" /></span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000001">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000003">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000004">‏טיול להודו נפאל פברואר 2013 ‏</span></p><p dir="rtl" class="pt-Normal-000005">‏<span class="pt-000006">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏לנוסעים שלום,‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏מועד הטיול הולך ומתקרב. אתם עומדים לצאת לחבל ארץ בתוך תת היבשת ההודית. הטיול הנו טיול מרתק. טיול שיפעיל את כל החושים. זה לא עוד טיול, אלא חוויה שאי-אפשר לחזור ממנה אדישים. ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏מקום שמעורר מחשבות רבות. מעלה הרבה מאוד סימני שאלה ויחד עם זאת רבים מהנוסעים חוזרים הביתה עם תחושה שהם רוצים לחזור ולתור בה פעם נוספת. מקווה שאתם תשובו עם הרגשה דומה.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏להלן מספר עצות לנוסעים:‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏הכנות טרום נסיעה ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏כדאי לפנות ללשכת הבריאות הקרובה למקום מגוריכם, בכדי להתייעץ עם המחלקה לטיולים ולקבל הדרכה לגבי החיסונים הדרושים לפי מצב הבריאות שלכם.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ויזות,דרכונים,ביטוח ותיקי יד -‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏1.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏תוקף הדרכון לפחות חצי שנה.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏2.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ויזה להודו (כניסה כפולה). הויזה לנפאל נעשית בכניסה לנפאל, יש להצטייד בשתי תמונות פספורט להוצאת הויזה. ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏עלות ויזה הוא 25$ לנוסע. כ"א מהנוסעים ישלם סכום זה בכניסה.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏3.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ביטוח רפואי ומטען: כדאי לעשות ביטוח רפואי הכולל פינוי בהיטס ולא להסתפק בביטוח סטנדרטי של חברות כרטיסי האשראי.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏4.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏יש לצלם את העמודים הרלוונטים מהדרכון (פרטים וויזה) וכן צילום של כרטיסי הטיסה ולהטמינם במזוודה.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏5.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏בתיק היד/מזוודת היד רצוי להכניס חליפת בגדים להחלפה ותרופות.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏6.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏בתיקי היד יש לקחת מיכלים (שפורפרות ובקבוקים) המכילים עד 50 ממ"ל.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏7.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏משקל מזוודת המטען לא יעלה על 20 ק"ג.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏הטיסה מנתב"ג ב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 25/2/2013, מספר טיסה ‏</span><span class="pt-DefaultParagraphFont-000013">‎TK ‎</span><span class="pt-DefaultParagraphFont-000013">‎–‎</span><span class="pt-DefaultParagraphFont-000013">‎ 787 ‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏את המטען יש לשלוח ישירות למובאי. ‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏יש להגיע לש"ת 3 לפני שעת ההמראה. כלומר,בשעה 12:30. אמתין לכם בשער 32 (על יד הדלפק שמצויין כנקודת מפגש לקבוצות). מבקשת מכל הנוסעים להגיע לנקודת המפגש עוד טרם תחילת תהליך הצ'ק אין.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏כסף והוצאות ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏כדאי להצטייד בדולר אמריקאי רצוי שהשטרות יהיו חדשים, כמו כן מומלץ להצטייד בשטרות הקטנים מ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 100$.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ההוצאות היומיות השוטפות לאדם הן כ- 20$ (אנחנו מקבלים ארוחת בוקר וערב).‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ סכום זה איננו כולל קניות.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏בנוסף לסכום הנ"ל -‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏כל נוסע ישלם 25$ עבור ויזה לנפאל.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏כל נוסע ישלם 30$ מס נמל בקטמנדו.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏רצוי לשמור את אחת מקבלות המרת הכספים וזאת בכדי שאפשר יהיה להחליף את הכסף העודף עם סיום הטיול.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏שערי ההמרה:‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL">#x200fכ- 53.9 רופי הודי ל #x200f</span><span lang="he-IL">#x200f–#x200f</span><span lang="he-IL">#x200f 1$.#x200f</span><span class="pt-DefaultParagraphFont"><span class="pt-000014"> </span></span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏14.66 רופי הודי = 1 ש"ח‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL">#x200fכ- 86.68 רופי נפאלי ל #x200f</span><span lang="he-IL">#x200f–#x200f</span><span lang="he-IL">#x200f 1$.#x200f</span><span class="pt-DefaultParagraphFont"><span class="pt-000014"> </span></span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏23.75 רופי נפלאי = 1 ₪‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏הפרשי זמנים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏שעון נפאל ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מקדים‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ את ישראל ב- 3 שעות ו- 45 דקות‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏שעון הודו ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מקדים‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ את שעון ישראל ב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 3 שעות ו ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 30 דקות‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏תקשורת ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏אינטרנט‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏טלפונים ציבוריים‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏טלפונים סלולריים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ השימוש יקר.ישנם אזורים שבהם אין כיסוי לטלפונים.‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏לבעלי הסמארט פון ובעלי קו בזק בבית, ממליצה להוריד את האפליקציה של בזק שנקראת בי-פון - ‏</span><span class="pt-DefaultParagraphFont-000013">‎BPHONE‎</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000018">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏צלמו את רשימת בתי המלון ומספרי הטלפון שתקבלו והשא‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ירו את הרשימה למשפחה.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏חשמל: 220 ‏</span><span class="pt-DefaultParagraphFont-000019">‎W‎</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‎ ‎</span></p><p dir="rtl" class="pt-Normal-000020">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏תרופות והיגיינ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏למשתמשים בתרופות באופן קבוע יש לקחת אותן בתיק היד. רצוי לקחת תרופות לשימוש אישי כגון: אקומול, אקומול קולד, תרופה נגד הרעלת קיבה, וכדורים מעוררי קיבה, אגדים נדבקים.‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ממליצה לקחת מטליות לחות וג'ל לחיטוי הידיים.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏אוכל ומים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏אפשר להביא מעט חטיפים, פירות יבשים וכו' לנשנוש בין הארוחות. (אל תעמיסו הרבה מדי !!).‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏למעונייני‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ם‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ באוכל צמחוני בטיסות יש לעדכן מבעוד מועד את סוכן הנסיעות שאצלו רכשתם את הטיול.‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏יש לשתות מי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ פולארי‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ס‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏. בעת קניית בקבוק מים יש לבדוק שהפקק הוא מקורי. ניתן לרכוש מים בכל מקום. אין צורך לקחת מהארץ.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ציוד אישי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏נעלי הליכה, כובע משקפי שמש, משקפי ראיה רזרביים, קרם הגנה, מעיל, מטריה.‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מבחינתלבוש כדאי להצטייד בלבוש הדומה לזה שאנו מתלבשים בימים אלה בארץ. הבוקר חמים ובערב עשוי להיות קריר/קר.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏נחמד יהיה באם תביאו עמכם מספר תקליטורי מוסיקה, ספור/משחק וכו'.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏הצלחת הטיול תלויה בכל אחד ואחת מאתנו. חלק מההצלחה הוא עמידה בלוחות זמנים וגילוי סבלנות וסובלנות . ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏חשוב להשאיר את טרדות היום יום בבית, להגיע לטיול עם לב פתוח, אוזן קשובה ועין פקוחה, בכדי שהטיול יהיה משמעותי וחוייתי.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏בתקווה שנצא בשלום ונשוב כולנו לשלום.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏בברכה,‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏שרה פרי‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000000">‏ ‏</span></p><p dir="ltr" class="pt-Normal-000021"><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏טלפונים: בבית ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 09-7425733 : נייד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 052-22315252‏</span></p><p dir="ltr" class="pt-Normal-000022"><span xml:space="preserve" class="pt-000000"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-02_files/image1.gif b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-02_files/image1.gif
new file mode 100644
index 0000000..ab5137f
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Hebrew-02_files/image1.gif
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/HtmlToWmlConverter01.cs b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/HtmlToWmlConverter01.cs
new file mode 100644
index 0000000..c5898d7
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/HtmlToWmlConverter01.cs
@@ -0,0 +1,220 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2012-2015.
+
+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
+
+Published at http://OpenXmlDeveloper.org
+Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx
+
+Developer: Eric White
+Blog: http://www.ericwhite.com
+Twitter: @EricWhiteDev
+Email: eric@ericwhite.com
+
+***************************************************************************/
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Validation;
+using OpenXmlPowerTools;
+using OpenXmlPowerTools.HtmlToWml;
+
+/*******************************************************************************************
+ * HtmlToWmlConverter expects the HTML to be passed as an XElement, i.e. as XML. While the HTML test files that
+ * are included in Open-Xml-PowerTools are able to be read as XML, most HTML is not able to be read as XML.
+ * The best solution is to use the HtmlAgilityPack, which can parse HTML and save as XML. The HtmlAgilityPack
+ * is licensed under the Ms-PL (same as Open-Xml-PowerTools) so it is convenient to include it in your solution,
+ * and thereby you can convert HTML to XML that can be processed by the HtmlToWmlConverter.
+ *
+ * A convenient way to get the DLL that has been checked out with HtmlToWmlConverter is to clone the repo at
+ * https://github.com/EricWhiteDev/HtmlAgilityPack
+ *
+ * That repo contains only the DLL that has been checked out with HtmlToWmlConverter.
+ *
+ * Of course, you can also get the HtmlAgilityPack source and compile it to get the DLL. You can find it at
+ * http://codeplex.com/HtmlAgilityPack
+ *
+ * We don't include the HtmlAgilityPack in Open-Xml-PowerTools, to simplify installation. The example files
+ * in this module do not require HtmlAgilityPack to run.
+*******************************************************************************************/
+
+class HtmlToWmlConverterExample
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ foreach (var file in Directory.GetFiles("../../", "*.html") /* .Where(f => f.Contains("Test-01")) */ )
+ {
+ ConvertToDocx(file, tempDi.FullName);
+ }
+ }
+
+ private static void ConvertToDocx(string file, string destinationDir)
+ {
+ bool s_ProduceAnnotatedHtml = true;
+
+ var sourceHtmlFi = new FileInfo(file);
+ Console.WriteLine("Converting " + sourceHtmlFi.Name);
+ var sourceImageDi = new DirectoryInfo(destinationDir);
+
+ var destCssFi = new FileInfo(Path.Combine(destinationDir, sourceHtmlFi.Name.Replace(".html", "-2.css")));
+ var destDocxFi = new FileInfo(Path.Combine(destinationDir, sourceHtmlFi.Name.Replace(".html", "-3-ConvertedByHtmlToWml.docx")));
+ var annotatedHtmlFi = new FileInfo(Path.Combine(destinationDir, sourceHtmlFi.Name.Replace(".html", "-4-Annotated.txt")));
+
+ XElement html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceHtmlFi);
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+ File.WriteAllText(destCssFi.FullName, usedAuthorCss);
+
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings();
+ // image references in HTML files contain the path to the subdir that contains the images, so base URI is the name of the directory
+ // that contains the HTML files
+ settings.BaseUriForImages = sourceHtmlFi.DirectoryName;
+
+ WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(defaultCss, usedAuthorCss, userCss, html, settings, null, s_ProduceAnnotatedHtml ? annotatedHtmlFi.FullName : null);
+ doc.SaveAs(destDocxFi.FullName);
+ }
+
+ public class HtmlToWmlReadAsXElement
+ {
+ public static XElement ReadAsXElement(FileInfo sourceHtmlFi)
+ {
+ string htmlString = File.ReadAllText(sourceHtmlFi.FullName);
+ XElement html = null;
+ try
+ {
+ html = XElement.Parse(htmlString);
+ }
+#if USE_HTMLAGILITYPACK
+ catch (XmlException)
+ {
+ HtmlDocument hdoc = new HtmlDocument();
+ hdoc.Load(sourceHtmlFi.FullName, Encoding.Default);
+ hdoc.OptionOutputAsXml = true;
+ hdoc.Save(sourceHtmlFi.FullName, Encoding.Default);
+ StringBuilder sb = new StringBuilder(File.ReadAllText(sourceHtmlFi.FullName, Encoding.Default));
+ sb.Replace("&", "&");
+ sb.Replace(" ", "\xA0");
+ sb.Replace(""", "\"");
+ sb.Replace("<", "~lt;");
+ sb.Replace(">", "~gt;");
+ sb.Replace("&#", "~#");
+ sb.Replace("&", "&");
+ sb.Replace("~lt;", "<");
+ sb.Replace("~gt;", ">");
+ sb.Replace("~#", "&#");
+ File.WriteAllText(sourceHtmlFi.FullName, sb.ToString(), Encoding.Default);
+ html = XElement.Parse(sb.ToString());
+ }
+#else
+ catch (XmlException e)
+ {
+ throw e;
+ }
+#endif
+ // HtmlToWmlConverter expects the HTML elements to be in no namespace, so convert all elements to no namespace.
+ html = (XElement)ConvertToNoNamespace(html);
+ return html;
+ }
+
+ private static object ConvertToNoNamespace(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name.LocalName,
+ element.Attributes().Where(a => !a.IsNamespaceDeclaration),
+ element.Nodes().Select(n => ConvertToNoNamespace(n)));
+ }
+ return node;
+ }
+ }
+
+ static string defaultCss =
+ @"html, address,
+blockquote,
+body, dd, div,
+dl, dt, fieldset, form,
+frame, frameset,
+h1, h2, h3, h4,
+h5, h6, noframes,
+ol, p, ul, center,
+dir, hr, menu, pre { display: block; unicode-bidi: embed }
+li { display: list-item }
+head { display: none }
+table { display: table }
+tr { display: table-row }
+thead { display: table-header-group }
+tbody { display: table-row-group }
+tfoot { display: table-footer-group }
+col { display: table-column }
+colgroup { display: table-column-group }
+td, th { display: table-cell }
+caption { display: table-caption }
+th { font-weight: bolder; text-align: center }
+caption { text-align: center }
+body { margin: auto; }
+h1 { font-size: 2em; margin: auto; }
+h2 { font-size: 1.5em; margin: auto; }
+h3 { font-size: 1.17em; margin: auto; }
+h4, p,
+blockquote, ul,
+fieldset, form,
+ol, dl, dir,
+menu { margin: auto }
+a { color: blue; }
+h5 { font-size: .83em; margin: auto }
+h6 { font-size: .75em; margin: auto }
+h1, h2, h3, h4,
+h5, h6, b,
+strong { font-weight: bolder }
+blockquote { margin-left: 40px; margin-right: 40px }
+i, cite, em,
+var, address { font-style: italic }
+pre, tt, code,
+kbd, samp { font-family: monospace }
+pre { white-space: pre }
+button, textarea,
+input, select { display: inline-block }
+big { font-size: 1.17em }
+small, sub, sup { font-size: .83em }
+sub { vertical-align: sub }
+sup { vertical-align: super }
+table { border-spacing: 2px; }
+thead, tbody,
+tfoot { vertical-align: middle }
+td, th, tr { vertical-align: inherit }
+s, strike, del { text-decoration: line-through }
+hr { border: 1px inset }
+ol, ul, dir,
+menu, dd { margin-left: 40px }
+ol { list-style-type: decimal }
+ol ul, ul ol,
+ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
+u, ins { text-decoration: underline }
+br:before { content: ""\A""; white-space: pre-line }
+center { text-align: center }
+:link, :visited { text-decoration: underline }
+:focus { outline: thin dotted invert }
+/* Begin bidirectionality settings (do not change) */
+BDO[DIR=""ltr""] { direction: ltr; unicode-bidi: bidi-override }
+BDO[DIR=""rtl""] { direction: rtl; unicode-bidi: bidi-override }
+*[DIR=""ltr""] { direction: ltr; unicode-bidi: embed }
+*[DIR=""rtl""] { direction: rtl; unicode-bidi: embed }
+
+";
+
+ static string userCss = @"";
+}
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/HtmlToWmlConverter01.csproj b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/HtmlToWmlConverter01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/HtmlToWmlConverter01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/ResumeTemplate.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/ResumeTemplate.html
new file mode 100644
index 0000000..58c9d65
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/ResumeTemplate.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Title {
+ margin-top: 0;
+ line-height: 150.0%;
+ margin-bottom: 0;
+ font-family: Microsoft YaHei UI;
+ font-size: 20pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-a {
+ color: #000000;
+ font-family: Microsoft YaHei UI;
+ font-size: 20pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #7F7F7F;
+ font-family: Microsoft YaHei UI;
+ font-size: 16pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-a1 {
+ line-height: 150.0%;
+ margin-bottom: 0;
+ font-family: Microsoft YaHei UI;
+ font-size: 5pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000000 {
+ color: #404040;
+ font-size: 5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-a0 {
+ line-height: 150.0%;
+ margin-bottom: 10pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000001 {
+ color: #404040;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+h1.pt-Heading1 {
+ margin-top: 10pt;
+ line-height: 150.0%;
+ margin-bottom: 0;
+ font-family: Microsoft YaHei UI;
+ font-size: 17pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000002 {
+ color: #000000;
+ font-family: Microsoft YaHei UI;
+ font-size: 17pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000003 {
+ border-collapse: collapse;
+ border: none;
+ margin-bottom: .001pt;
+}
+td.pt-000004 {
+ vertical-align: top;
+ width: 425.25pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+h2.pt-Heading2 {
+ margin-top: 1pt;
+ line-height: 150.0%;
+ margin-bottom: 0;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-Strong {
+ color: #404040;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal {
+ line-height: 150.0%;
+ margin-bottom: 0;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 78.75pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Date {
+ margin-top: 1pt;
+ line-height: 150.0%;
+ margin-bottom: 0;
+ text-align: right;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000006 {
+ color: #404040;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-Title"><span lang="zh-CN" xml:space="preserve" class="pt-a">[您的姓名] | </span><span lang="zh-CN" class="pt-DefaultParagraphFont">个人履历</span></p><p dir="ltr" class="pt-a1"><span xml:space="preserve" class="pt-000000"> </span></p><p dir="ltr" class="pt-a0"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[邮政编码,省/市/自治区,市/县,地址] [电话] [电子邮件地址]</span></p><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">教学经验</span></h1><div align="left"><table dir="ltr" class="pt-000003"><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[</span><span lang="zh-CN" class="pt-Strong">职务</span><span lang="zh-CN" class="pt-Strong">]</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000001"> - [课程或学院名称]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[在此处添加简短说明]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[起止年份]</span></p></td></tr><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[</span><span lang="zh-CN" class="pt-Strong">职务</span><span lang="zh-CN" class="pt-Strong">] -</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000001"> [课程或学院名称]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[要替换占位符文本(例如此文本),只需选择它并开始键入。请勿包括您的所选内容左侧或右侧的空格。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[起止年份]</span></p></td></tr><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[</span><span lang="zh-CN" class="pt-Strong">职务</span><span lang="zh-CN" xml:space="preserve" class="pt-Strong">] - </span><span class="pt-DefaultParagraphFont-000001">[课程或学院名称]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[要在此简历的任何表格中添加或删除行,只需单击某行,然后在功能区的“表格工具”下的“布局”选项卡上,单击“插入”或“删除”选项。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[起止年份]</span></p></td></tr></table></div><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">教育</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">背景</span></h1><div align="left"><table dir="ltr" class="pt-000003"><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" xml:space="preserve" class="pt-Strong">[获得的学位] - </span><span class="pt-DefaultParagraphFont-000001">[学院,位置]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[只需从“开始”选项卡的“样式”组中单击,即可应用您在此简历中看到的任何文本格式。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[起止年份]</span></p></td></tr><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" xml:space="preserve" class="pt-Strong">[获得的学位] - </span><span class="pt-DefaultParagraphFont-000001">[学院,位置]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[节标题使用“标题 1”样式。每个经验或教育条目的标题使用“标题 2”样式。此文本使用“普通”样式。右对齐文本使用“日期”样式。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[起止年份]</span></p></td></tr></table></div><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">发表作品</span></h1><div align="left"><table dir="ltr" class="pt-000003"><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[</span><span lang="zh-CN" class="pt-Strong">发表作品</span><span lang="zh-CN" class="pt-Strong">名称]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[在此处添加简短说明]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">发表</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">日期]</span></p></td></tr><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[</span><span lang="zh-CN" class="pt-Strong">发表作品</span><span lang="zh-CN" class="pt-Strong">名称]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[在此处添加简短说明]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">发表</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">日期]</span></p></td></tr></table></div><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">获奖经历</span></h1><div align="left"><table dir="ltr" class="pt-000003"><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[奖</span><span lang="zh-CN" class="pt-Strong">项</span><span lang="zh-CN" class="pt-Strong">名称],</span><span class="pt-DefaultParagraphFont-000001">[学院]</span></h2></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">获奖</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">日期]</span></p></td></tr><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[奖</span><span lang="zh-CN" class="pt-Strong">项</span><span lang="zh-CN" class="pt-Strong">名称],</span><span class="pt-DefaultParagraphFont-000001">[学院]</span></h2></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">获奖</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">日期]</span></p></td></tr></table></div><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">相关经验</span></h1><div align="left"><table dir="ltr" class="pt-000003"><tr><td class="pt-000004"><h2 dir="ltr" class="pt-Heading2"><span lang="zh-CN" class="pt-Strong">[</span><span lang="zh-CN" class="pt-Strong">职务</span><span lang="zh-CN" class="pt-Strong">] -</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000001"> [</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">组织名称,地点</span><span class="pt-DefaultParagraphFont-000001">]</span></h2><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[在此处添加简短说明]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Date"><span lang="zh-CN" class="pt-DefaultParagraphFont-000001">[起止年份]</span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000006"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/TaskPlanTemplate.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/TaskPlanTemplate.html
new file mode 100644
index 0000000..db2415e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/TaskPlanTemplate.html
@@ -0,0 +1,483 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-a {
+ margin-bottom: 28pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #000000;
+ font-family: Microsoft YaHei UI;
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ margin-bottom: .001pt;
+}
+td.pt-000001 {
+ vertical-align: top;
+ width: 481.9pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #1FB1E6 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+p.pt-Title {
+ margin-bottom: 4pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 24pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000002 {
+ color: #1FB1E6;
+ font-family: Microsoft YaHei UI;
+ font-size: 24pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Subtitle {
+ margin-top: 2pt;
+ line-height: 120.0%;
+ margin-bottom: 14pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 10pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+p.pt-Normal {
+ line-height: 120.0%;
+ margin-bottom: 14pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 8.5pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000003 {
+ color: #595959;
+ font-family: Microsoft YaHei UI;
+ font-size: 8.5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000004 {
+ border-collapse: collapse;
+ border: none;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000005 {
+ vertical-align: bottom;
+ width: 287.95pt;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #8FB931 1.0pt;
+ padding-left: 5.4pt;
+ background: #8FB931;
+}
+p.pt-Normal-000006 {
+ margin-top: 4pt;
+ margin-bottom: 4pt;
+ text-align: center;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000007 {
+ color: #FFFFFF;
+ font-family: Microsoft YaHei UI;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000008 {
+ vertical-align: bottom;
+ width: 79.15pt;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F78303;
+}
+td.pt-000009 {
+ vertical-align: bottom;
+ width: 57.55pt;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F78303;
+}
+td.pt-000010 {
+ vertical-align: bottom;
+ width: 57.25pt;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #F0628B 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F0628B;
+}
+td.pt-000011 {
+ vertical-align: top;
+ width: 287.95pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+p.pt-Normal-000012 {
+ margin-top: 4pt;
+ line-height: 120.0%;
+ margin-bottom: 4pt;
+ text-align: center;
+ font-family: Microsoft YaHei UI;
+ font-size: 8.5pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000013 {
+ color: #595959;
+ font-size: 8.5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000014 {
+ vertical-align: top;
+ width: 79.15pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000015 {
+ vertical-align: top;
+ width: 57.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+p.pt-Normal-000016 {
+ margin-top: 4pt;
+ line-height: 120.0%;
+ margin-bottom: 4pt;
+ text-align: center;
+ font-family: Microsoft YaHei UI;
+ font-size: 10pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000017 {
+ color: #404040;
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000018 {
+ vertical-align: top;
+ width: 57.25pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000019 {
+ vertical-align: top;
+ width: 287.95pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000020 {
+ vertical-align: top;
+ width: 79.15pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000021 {
+ vertical-align: top;
+ width: 57.55pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000022 {
+ vertical-align: top;
+ width: 57.25pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000023 {
+ vertical-align: top;
+ width: 287.95pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000024 {
+ vertical-align: top;
+ width: 79.15pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000025 {
+ vertical-align: top;
+ width: 57.55pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000026 {
+ vertical-align: top;
+ width: 57.25pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000027 {
+ vertical-align: top;
+ width: 287.95pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000028 {
+ vertical-align: top;
+ width: 79.15pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000029 {
+ vertical-align: top;
+ width: 57.55pt;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000030 {
+ vertical-align: top;
+ width: 287.95pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000031 {
+ vertical-align: top;
+ width: 79.15pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000032 {
+ vertical-align: top;
+ width: 57.55pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000033 {
+ vertical-align: top;
+ width: 57.25pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000034 {
+ vertical-align: top;
+ width: 287.95pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000035 {
+ vertical-align: top;
+ width: 79.15pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000036 {
+ vertical-align: top;
+ width: 57.55pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000037 {
+ vertical-align: top;
+ width: 57.25pt;
+ border-top: solid #7F7F7F 1.0pt;
+ padding-top: 0;
+ border-right: solid #7F7F7F 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #7F7F7F 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #7F7F7F 1.0pt;
+ padding-left: 5.4pt;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-a"><span lang="zh-CN" class="pt-DefaultParagraphFont">[日期]</span></p><div align="left"><table dir="ltr" class="pt-000000"><tr><td class="pt-000001"><p dir="ltr" class="pt-Title"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">[项目名称]</span></p></td></tr></table></div><p dir="ltr" class="pt-Subtitle"><span lang="zh-CN" class="pt-DefaultParagraphFont">[学生姓名]</span><span lang="zh-CN" xml:space="preserve" class="pt-DefaultParagraphFont"> </span><span lang="zh-CN" class="pt-DefaultParagraphFont">|</span><span lang="zh-CN" xml:space="preserve" class="pt-DefaultParagraphFont"> </span><span lang="zh-CN" class="pt-DefaultParagraphFont">[课程名称]</span><span lang="zh-CN" xml:space="preserve" class="pt-DefaultParagraphFont"> </span><span lang="zh-CN" class="pt-DefaultParagraphFont">|</span><span lang="zh-CN" xml:space="preserve" class="pt-DefaultParagraphFont"> </span><span lang="zh-CN" class="pt-DefaultParagraphFont">[教师姓名/上课时间/课时]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" xml:space="preserve" class="pt-DefaultParagraphFont-000003">需要在任务表中添加更多行?没问题。只需单击表格的最后一个单元格,然后按 Tab 键即可。 </span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000003">或者,要在表格中间添加行,请单击某一行,然后在功能区的“表格工具”下的“布局”选项卡中单击“在上方插入”或“在下方插入”。</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000003">要将(像这样的)占位符文本替换为您自己的文本,只需选中并开始键入即可。请勿在您选中的字符右侧或左侧包含空格。</span></p><div align="left"><table dir="ltr" class="pt-000004"><tr><td class="pt-000005"><p dir="ltr" class="pt-Normal-000006"><span lang="zh-CN" class="pt-DefaultParagraphFont-000007">任务</span></p></td><td class="pt-000008"><p dir="ltr" class="pt-Normal-000006"><span lang="zh-CN" class="pt-DefaultParagraphFont-000007">到期日</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000006"><span lang="zh-CN" class="pt-DefaultParagraphFont-000007">已完成</span></p></td><td class="pt-000010"><p dir="ltr" class="pt-Normal-000006"><span lang="zh-CN" class="pt-DefaultParagraphFont-000007">签名</span></p></td></tr><tr><td class="pt-000011"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000019"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000020"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000021"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000022"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000023"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000025"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000019"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000020"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000021"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000022"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000023"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000025"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000019"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000020"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000021"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000022"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000023"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000025"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000019"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000020"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000021"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000022"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000023"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000025"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000027"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000028"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000029"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000022"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000030"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000031"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000032"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000033"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000035"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000030"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000031"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000032"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000033"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000035"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal-000016"><span xml:space="preserve" class="pt-000017"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000013"> </span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000013"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-01.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-01.html
new file mode 100644
index 0000000..a74237b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-01.html
@@ -0,0 +1,255 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ margin-bottom: .001pt;
+}
+tr.pt-000001 {
+ height: 0.70in;
+}
+td.pt-000002 {
+ vertical-align: top;
+ width: 48.25pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-NoSpacing {
+ margin-top: 0;
+ font-family: Franklin Gothic Medium;
+ font-size: 9pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000003 {
+ color: #27130E;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000004 {
+ vertical-align: middle;
+ width: 7.9pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal {
+ margin-top: 6pt;
+ line-height: 105.0%;
+ font-family: Franklin Gothic Medium;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000005 {
+ vertical-align: middle;
+ width: 447.85pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+table.pt-000006 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-bottom: .001pt;
+}
+tr.pt-000007 {
+ height: 0.06in;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 100.0%;
+ border-top: solid #4F271C 1.0pt;
+ padding-top: 0;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #4F271C 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+}
+tr.pt-000009 {
+ height: 0.50in;
+}
+td.pt-000010 {
+ vertical-align: middle;
+ width: 100.0%;
+ border-top: solid #4F271C 1.0pt;
+ padding-top: 0;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-bottom: 0;
+}
+p.pt-Title {
+ margin-top: 2pt;
+ line-height: 85.0%;
+ margin-left: 0.10in;
+ font-family: Franklin Gothic Medium;
+ font-size: 32pt;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont {
+ color: #3891A7;
+ font-family: Franklin Gothic Medium;
+ font-size: 32pt;
+ text-transform: uppercase;
+ letter-spacing: 0.5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000011 {
+ height: 0.10in;
+}
+td.pt-000012 {
+ vertical-align: top;
+ width: 100.0%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #4F271C;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+div.pt-000013 {
+ border-top: none;
+ border-right: none;
+ border-bottom: double #4F271C 5.3pt;
+ padding-bottom: 1.0pt;
+ border-left: none;
+ margin-left: 0;
+}
+h1.pt-000014 {
+ margin-top: 31pt;
+ line-height: 105.0%;
+ margin-bottom: 3pt;
+ margin-left: 0.25in;
+ text-indent: -0.25in;
+ font-family: Franklin Gothic Medium;
+ font-size: 12pt;
+ margin-right: 0;
+}
+span.pt-000015 {
+ color: #3891A7;
+ font-family: Franklin Gothic Medium;
+ font-size: 12pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-DefaultParagraphFont-000016 {
+ color: #3891A7;
+ font-family: Franklin Gothic Medium;
+ font-size: 12pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000017 {
+ vertical-align: top;
+ width: 4.4%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Checkbox {
+ margin-top: 3pt;
+ line-height: 105.0%;
+ font-family: Segoe UI Symbol;
+ font-size: 10.5pt;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000018 {
+ color: #2A6C7D;
+ font-family: Segoe UI Symbol;
+ font-size: 10.5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000019 {
+ vertical-align: top;
+ width: 95.6%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-List {
+ margin-top: 6pt;
+ line-height: 105.0%;
+ margin-right: 0.50in;
+ font-family: Franklin Gothic Medium;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000020 {
+ color: #27130E;
+ font-family: Franklin Gothic Medium;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+h1.pt-000021 {
+ margin-top: 20pt;
+ line-height: 105.0%;
+ margin-bottom: 3pt;
+ margin-left: 0.25in;
+ text-indent: -0.25in;
+ font-family: Franklin Gothic Medium;
+ font-size: 12pt;
+ margin-right: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><div align="right"><table dir="ltr" class="pt-000000"><tr class="pt-000001"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000005"><div align="left"><table dir="ltr" class="pt-000006"><tr class="pt-000007"><td class="pt-000008"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000009"><td class="pt-000010"><p dir="ltr" class="pt-Title"><span class="pt-DefaultParagraphFont">Business Trip Checklist</span></p></td></tr><tr class="pt-000011"><td class="pt-000012"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><div class="pt-000013"><h1 dir="ltr" class="pt-000014"><span class="pt-000015">1.</span><span class="pt-DefaultParagraphFont-000016">While You Are Away: Preparing the Office</span></h1></div><div align="left"><table dir="ltr" class="pt-000006"><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☒</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Organize any necessary meetings to take place on your trip; book appointments and meeting rooms.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">If traveling internationally, obtain any necessary paperwork and vaccinations.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Familiarize yourself with local business customs common at destination.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Confirm appointments, schedules, reservations, etc.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Tie up any loose ends at the office (finish up projects; set up out-of-office replies; notify or remind coworkers about your departure).</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Print out hard copies of presentations, agendas, and important documents.</span></p></td></tr></table></div><div class="pt-000013"><h1 dir="ltr" class="pt-000021"><span class="pt-000015">2.</span><span class="pt-DefaultParagraphFont-000016">While You Are Away: Preparing the Home</span></h1></div><div align="left"><table dir="ltr" class="pt-000006"><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Arrange for child, pet, and plant care; communicate needs and schedules.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Pause routine deliveries.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Make your home seem lived-in while away by putting lights and a radio on timers.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Turn down thermostat.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Leave house and car keys, and your complete itinerary, with a trusted friend.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Lock windows, garages, and doors.</span></p></td></tr></table></div><div class="pt-000013"><h1 dir="ltr" class="pt-000021"><span class="pt-000015">3.</span><span class="pt-DefaultParagraphFont-000016">Packing for the Trip</span></h1></div><div align="left"><table dir="ltr" class="pt-000006"><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Make a list of the specific items of clothing you’ll need to pack for your trip to suit the various functions you’ll attend.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Try to pack everything you need in a carry-on bag, to avoid the possibility of lost luggage.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">If you check your bag, pack a second set of business clothes and toiletries in a carry-on bag, in case of lost luggage.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Print several copies of this checklist, and save a copy on your computer’s hard drive to refer to when planning your next trip. Storing the checklist on your computer is the easiest way to make updates to it when necessary.</span></p></td></tr></table></div><div class="pt-000013"><h1 dir="ltr" class="pt-000021"><span class="pt-000015">4.</span><span class="pt-DefaultParagraphFont-000016">What to Leave for Family and Caregivers at Home</span></h1></div><div align="left"><table dir="ltr" class="pt-000006"><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Leave your contact information—including the names, addresses, and phone numbers of the hotels where you are staying—with a family member, so they can reach you while you’re away.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Phone numbers (all contact numbers for you; doctor/vet; pharmacy; mechanic; school/daycare; helpful friends/neighbors; alarm company).</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Cash for groceries and emergencies.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Consent for medical treatment forms and insurance cards.</span></p></td></tr><tr><td class="pt-000017"><p dir="ltr" class="pt-Checkbox"><span class="pt-DefaultParagraphFont-000018">☐</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-List"><span class="pt-DefaultParagraphFont-000020">Your travel itinerary.</span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-02.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-02.html
new file mode 100644
index 0000000..7455d96
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-02.html
@@ -0,0 +1,686 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Subtitle {
+ margin-top: 0;
+ margin-bottom: 25pt;
+ font-family: Century Gothic;
+ font-size: 10pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #5A5A5A;
+ font-family: Century Gothic;
+ font-size: 10pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-bottom: .001pt;
+}
+td.pt-000001 {
+ vertical-align: top;
+ width: 12.6%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #000000 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+p.pt-Title {
+ margin-top: 0;
+ margin-bottom: 4pt;
+ font-family: Century Gothic;
+ font-size: 16pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000002 {
+ color: #000000;
+ font-family: Century Gothic;
+ font-size: 16pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000003 {
+ vertical-align: top;
+ width: 32.6%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #000000 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+td.pt-000004 {
+ vertical-align: top;
+ width: 14.0%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #000000 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 18.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #000000 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+td.pt-000006 {
+ vertical-align: top;
+ width: 10.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #000000 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+td.pt-000007 {
+ vertical-align: top;
+ width: 11.7%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: double #000000 5.3pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+p.pt-Normal {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ font-family: Century Gothic;
+ font-size: 8.5pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000008 {
+ color: #595959;
+ font-size: 8.5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000009 {
+ vertical-align: bottom;
+ width: 20.1%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+table.pt-000010 {
+ border-collapse: collapse;
+ border: none;
+ width: 61%;
+ margin-bottom: .001pt;
+}
+td.pt-000011 {
+ vertical-align: bottom;
+ width: 48.1%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Days {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-family: Century Gothic;
+ font-size: 9pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000012 {
+ color: #595959;
+ font-family: Century Gothic;
+ font-size: 9pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000013 {
+ vertical-align: bottom;
+ width: 51.9%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+p.pt-Days-000014 {
+ margin-top: 0;
+ margin-bottom: 0;
+ text-align: center;
+ font-family: Century Gothic;
+ font-size: 9pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000015 {
+ color: #595959;
+ font-size: 9pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000016 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+table.pt-000017 {
+ border-collapse: collapse;
+ border: none;
+ width: 58%;
+ margin-bottom: .001pt;
+}
+td.pt-000018 {
+ vertical-align: bottom;
+ width: 45.9%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000019 {
+ vertical-align: bottom;
+ width: 54.1%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+table.pt-000020 {
+ border-collapse: collapse;
+ border: none;
+ width: 59%;
+ margin-bottom: .001pt;
+}
+td.pt-000021 {
+ vertical-align: bottom;
+ width: 47.1%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000022 {
+ vertical-align: bottom;
+ width: 52.9%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+table.pt-000023 {
+ border-collapse: collapse;
+ border: none;
+ width: 50%;
+ margin-bottom: .001pt;
+}
+td.pt-000024 {
+ vertical-align: bottom;
+ width: 37.6%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000025 {
+ vertical-align: bottom;
+ width: 62.4%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+}
+table.pt-000026 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000027 {
+ vertical-align: top;
+ width: 20.1%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #1FB1E6 1.0pt;
+ padding-left: 5.4pt;
+ background: #1FB1E6;
+}
+p.pt-Normal-000028 {
+ margin-top: 4pt;
+ margin-bottom: 4pt;
+ font-family: Century Gothic;
+ font-size: 9pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000029 {
+ color: #FFFFFF;
+ font-family: Century Gothic;
+ font-size: 9pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000030 {
+ vertical-align: top;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #1FB1E6;
+}
+p.pt-Normal-000031 {
+ margin-top: 4pt;
+ margin-bottom: 4pt;
+ font-family: Century Gothic;
+ font-size: 8.5pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000032 {
+ color: #595959;
+ font-size: 8.5pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000033 {
+ vertical-align: top;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #1FB1E6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #1FB1E6;
+}
+td.pt-000034 {
+ vertical-align: top;
+ width: 20.1%;
+ border-top: solid #D9D9D9 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+span.pt-DefaultParagraphFont-000035 {
+ color: #595959;
+ font-family: Century Gothic;
+ font-size: 8.5pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000036 {
+ vertical-align: top;
+ width: 20.0%;
+ border-top: solid #D9D9D9 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000037 {
+ vertical-align: top;
+ width: 20.0%;
+ border-top: solid #D9D9D9 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000038 {
+ vertical-align: top;
+ width: 20.0%;
+ border-top: solid #A6A6A6 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+ background: #F2F2F2;
+}
+td.pt-000039 {
+ vertical-align: top;
+ width: 20.1%;
+ border-top: solid #D9D9D9 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+td.pt-000040 {
+ vertical-align: top;
+ width: 20.0%;
+ border-top: solid #D9D9D9 1.0pt;
+ padding-top: 0;
+ border-right: solid #A6A6A6 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #A6A6A6 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A6A6A6 1.0pt;
+ padding-left: 5.4pt;
+}
+p.pt-TableSpace {
+ margin-top: 0;
+ line-height: 3.6pt;
+ margin-bottom: 0;
+ font-family: Century Gothic;
+ font-size: 8.5pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+td.pt-000041 {
+ vertical-align: bottom;
+ width: 20.1%;
+ border-top: solid #8FB931 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #8FB931 1.0pt;
+ padding-left: 5.4pt;
+ background: #8FB931;
+}
+td.pt-000042 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top: solid #8FB931 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #8FB931;
+}
+td.pt-000043 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top: solid #8FB931 1.0pt;
+ padding-top: 0;
+ border-right: solid #8FB931 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #8FB931;
+}
+td.pt-000044 {
+ vertical-align: bottom;
+ width: 20.1%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #F78303 1.0pt;
+ padding-left: 5.4pt;
+ background: #F78303;
+}
+td.pt-000045 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F78303;
+}
+td.pt-000046 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #F78303 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F78303;
+}
+td.pt-000047 {
+ vertical-align: bottom;
+ width: 20.1%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #F0628B 1.0pt;
+ padding-left: 5.4pt;
+ background: #F0628B;
+}
+td.pt-000048 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F0628B;
+}
+td.pt-000049 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #F0628B 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #F0628B;
+}
+td.pt-000050 {
+ vertical-align: bottom;
+ width: 20.1%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #A053A5 1.0pt;
+ padding-left: 5.4pt;
+ background: #A053A5;
+}
+td.pt-000051 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #A053A5;
+}
+td.pt-000052 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #A053A5 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #A053A5;
+}
+td.pt-000053 {
+ vertical-align: bottom;
+ width: 20.1%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #808080 1.0pt;
+ padding-left: 5.4pt;
+ background: #808080;
+}
+td.pt-000054 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #808080;
+}
+td.pt-000055 {
+ vertical-align: bottom;
+ width: 20.0%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #808080 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #D9D9D9 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #808080;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-Subtitle"><span class="pt-DefaultParagraphFont">Weekly Assignments</span></p><div align="left"><table dir="ltr" class="pt-000000"><tr><td class="pt-000001"><p dir="ltr" class="pt-Title"><span class="pt-DefaultParagraphFont-000002">NAME:</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Title"><span class="pt-DefaultParagraphFont-000002">Eric White</span></p></td><td class="pt-000004"><p dir="ltr" class="pt-Title"><span xml:space="preserve" class="pt-DefaultParagraphFont-000002">MONTH: </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Title"><span class="pt-DefaultParagraphFont-000002">January</span></p></td><td class="pt-000006"><p dir="ltr" class="pt-Title"><span class="pt-DefaultParagraphFont-000002">YEAR:</span></p></td><td class="pt-000007"><p dir="ltr" class="pt-Title"><span class="pt-DefaultParagraphFont-000002">2014</span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000000"><tr><td class="pt-000009"><div align="center"><table dir="ltr" class="pt-000010"><tr><td class="pt-000011"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000012">Mon:</span></p></td><td class="pt-000013"><p dir="ltr" class="pt-Days-000014"><span class="pt-DefaultParagraphFont-000012">1/27</span></p></td></tr></table></div><p dir="ltr" class="pt-Days"><span xml:space="preserve" class="pt-000015"> </span></p></td><td class="pt-000016"><div align="center"><table dir="ltr" class="pt-000017"><tr><td class="pt-000018"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000012">Tues:</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-Days-000014"><span class="pt-DefaultParagraphFont-000012">1/28</span></p></td></tr></table></div><p dir="ltr" class="pt-Days"><span xml:space="preserve" class="pt-000015"> </span></p></td><td class="pt-000016"><div align="center"><table dir="ltr" class="pt-000017"><tr><td class="pt-000018"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000012">Wed:</span></p></td><td class="pt-000019"><p dir="ltr" class="pt-Days-000014"><span class="pt-DefaultParagraphFont-000012">1/29</span></p></td></tr></table></div><p dir="ltr" class="pt-Days"><span xml:space="preserve" class="pt-000015"> </span></p></td><td class="pt-000016"><div align="center"><table dir="ltr" class="pt-000020"><tr><td class="pt-000021"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000012">Thur:</span></p></td><td class="pt-000022"><p dir="ltr" class="pt-Days-000014"><span class="pt-DefaultParagraphFont-000012">1/30</span></p></td></tr></table></div><p dir="ltr" class="pt-Days"><span xml:space="preserve" class="pt-000015"> </span></p></td><td class="pt-000016"><div align="center"><table dir="ltr" class="pt-000023"><tr><td class="pt-000024"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000012">Fri:</span></p></td><td class="pt-000025"><p dir="ltr" class="pt-Days-000014"><span class="pt-DefaultParagraphFont-000012">1/31</span></p></td></tr></table></div><p dir="ltr" class="pt-Days"><span xml:space="preserve" class="pt-000015"> </span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000026"><tr><td class="pt-000027"><p dir="ltr" class="pt-Normal-000028"><span class="pt-DefaultParagraphFont-000029">Math</span></p></td><td class="pt-000030"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000030"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000030"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000033"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Read pages 24-50</span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Read Book2 1-24</span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Problems on page 51</span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Word problem on 25</span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000039"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpace"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000026"><tr><td class="pt-000041"><p dir="ltr" class="pt-Normal-000028"><span class="pt-DefaultParagraphFont-000029">English</span></p></td><td class="pt-000042"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000042"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000042"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000043"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Report on Wind in the willows</span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Read Catcher in the Rye</span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000039"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpace"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000026"><tr><td class="pt-000044"><p dir="ltr" class="pt-Normal-000028"><span class="pt-DefaultParagraphFont-000029">SOCIAL STUDIES</span></p></td><td class="pt-000045"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000045"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000045"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000046"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Current news report</span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000039"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpace"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000026"><tr><td class="pt-000047"><p dir="ltr" class="pt-Normal-000028"><span class="pt-DefaultParagraphFont-000029">Science</span></p></td><td class="pt-000048"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000048"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000048"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000049"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Lab 3-5</span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Lab 3-5</span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000035">Lab 3-5</span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000039"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpace"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000026"><tr><td class="pt-000050"><p dir="ltr" class="pt-Normal-000028"><span class="pt-DefaultParagraphFont-000029">Geography</span></p></td><td class="pt-000051"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000051"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000051"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000052"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000039"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpace"><span xml:space="preserve" class="pt-000008"> </span></p><div align="left"><table dir="ltr" class="pt-000026"><tr><td class="pt-000053"><p dir="ltr" class="pt-Normal-000028"><span class="pt-DefaultParagraphFont-000029">French</span></p></td><td class="pt-000054"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000054"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000054"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td><td class="pt-000055"><p dir="ltr" class="pt-Normal-000031"><span xml:space="preserve" class="pt-000032"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000036"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000034"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000037"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr><tr><td class="pt-000039"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000038"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td><td class="pt-000040"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000008"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-03.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-03.html
new file mode 100644
index 0000000..f3cdd10
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-03.html
@@ -0,0 +1,335 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-bottom: .001pt;
+}
+tr.pt-000001 {
+ height: 0.20in;
+}
+td.pt-000002 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #88C589;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-NoSpacing {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000003 {
+ color: #595959;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000004 {
+ vertical-align: top;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #88C589;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #FFFFFF;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-TableSpacing {
+ margin-top: 0;
+ line-height: 2.0pt;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 2pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000006 {
+ color: #595959;
+ font-size: 2pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000007 {
+ vertical-align: top;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #FFFFFF;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+tr.pt-000008 {
+ height: 1.60in;
+}
+td.pt-000009 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #D7EBD7;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #595959;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: bottom;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #D7EBD7;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-MonthYear {
+ margin-top: 2pt;
+ margin-bottom: 12pt;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 44pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-Month {
+ color: #234824;
+ font-family: Calibri;
+ font-size: 44pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000011 {
+ color: #356D36;
+ font-family: Calibri;
+ font-size: 36pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000012 {
+ height: 0.25in;
+}
+table.pt-000013 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+tr.pt-000014 {
+ height: 0.40in;
+}
+td.pt-000015 {
+ vertical-align: middle;
+ width: 14.3%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #479249;
+}
+p.pt-Days {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 14pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000016 {
+ color: #FFFFFF;
+ font-family: Calibri;
+ font-size: 14pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000017 {
+ height: 0.30in;
+}
+td.pt-000018 {
+ vertical-align: top;
+ width: 14.3%;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: solid #88C589 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #88C589 1.0pt;
+ padding-left: 5.4pt;
+}
+p.pt-Dates {
+ margin-top: 6pt;
+ margin-bottom: 2pt;
+ margin-right: 0.10in;
+ font-family: Calibri;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-left: 0;
+}
+span.pt-000019 {
+ color: #262626;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000020 {
+ color: #262626;
+ font-family: Calibri;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000021 {
+ height: 0.70in;
+}
+p.pt-Normal {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ font-family: Calibri;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000022 {
+ color: #595959;
+ font-family: Calibri;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000023 {
+ height: 1.00in;
+}
+td.pt-000024 {
+ vertical-align: top;
+ width: 32.4pt;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #88C589 1.0pt;
+ padding-left: 5.4pt;
+ background: white;
+}
+p.pt-NoteHeading {
+ margin-top: 0;
+ margin-bottom: 0;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 18pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000025 {
+ color: #479249;
+ font-family: Calibri;
+ font-size: 18pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000026 {
+ vertical-align: middle;
+ width: 482.4pt;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: solid #88C589 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: white;
+}
+p.pt-Notes {
+ margin-top: 3pt;
+ line-height: 115.0%;
+ margin-bottom: 3pt;
+ font-family: Calibri;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000027 {
+ color: #595959;
+ font-family: Calibri;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000001"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr><td class="pt-000005"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td><td class="pt-000007"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td></tr><tr class="pt-000008"><td class="pt-000009"><p dir="ltr" class="pt-NoSpacing"><span class="pt-DefaultParagraphFont"><img src="Test-03_files/image1.jpeg" style="width: 2.333333in; height: 1.60071in" alt="Sample Photo" /></span></p></td><td class="pt-000010"><p dir="ltr" class="pt-MonthYear"><span class="pt-Month">January</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000011"> </span><span class="pt-DefaultParagraphFont-000011">2014</span></p></td></tr><tr><td class="pt-000005"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td><td class="pt-000007"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td></tr><tr class="pt-000012"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p><div align="left"><table dir="ltr" class="pt-000013"><tr class="pt-000014"><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Sun.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Mon.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Tue.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Wed.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Thu.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Fri.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Sat.</span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">1</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">2</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">3</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">4</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">5</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">6</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">7</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">8</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">9</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">10</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">11</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Little League</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Jane’s Birthday</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">12</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">13</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">14</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">15</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">16</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">17</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">18</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Parent Teacher Conference</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">19</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">20</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">21</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">22</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">23</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">24</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">25</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Run for Life 5K</span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">26</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">27</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">28</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">29</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">30</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">31</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000023"><td class="pt-000024"><p dir="ltr" class="pt-NoteHeading"><span class="pt-DefaultParagraphFont-000025">notes</span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Notes"><span xml:space="preserve" class="pt-DefaultParagraphFont-000027">To replace the picture with your own, right-click it and then click Change Picture. </span></p><p dir="ltr" class="pt-Notes"><span xml:space="preserve" class="pt-DefaultParagraphFont-000027">To select new dates or change the calendar color, click the Calendar tab on the ribbon. </span></p><p dir="ltr" class="pt-Notes"><span class="pt-DefaultParagraphFont-000027">To try out a different set of fonts or colors, on the Calendar tab or Design tab, click Fonts or Colors.</span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-03_files/image1.jpeg b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-03_files/image1.jpeg
new file mode 100644
index 0000000..eb8c961
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-03_files/image1.jpeg
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-04.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-04.html
new file mode 100644
index 0000000..d58a692
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-04.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title>C:\Users\Eric\Documents\Open-Xml-PowerTools\OpenXmlPowerToolsExamples\HtmlConverter01\Test-04.docx</title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+h1.pt-Heading1 {
+ margin-top: 18pt;
+ margin-bottom: 6pt;
+ margin-left: 0;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 18pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: black;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 18pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+h2.pt-Heading2 {
+ margin-top: 24pt;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000000 {
+ color: #343E5F;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 12pt;
+ letter-spacing: 0.1pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-BodyText {
+ margin-bottom: 6pt;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000001 {
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+h6.pt-Heading6 {
+ margin-top: 12pt;
+ margin-bottom: 2pt;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000002 {
+ font-family: 'Arial', 'sans-serif';
+ font-size: 8pt;
+ text-transform: uppercase;
+ letter-spacing: 0.8pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000003 {
+ margin-top: 3pt;
+ margin-bottom: 0;
+ margin-left: 0.75in;
+ text-indent: -0.25in;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-000004 {
+ font-family: Wingdings;
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+p.pt-000005 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ margin-left: 0.75in;
+ text-indent: -0.25in;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+p.pt-000006 {
+ margin-bottom: 0;
+ margin-left: 0.50in;
+ text-indent: -0.25in;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-000007 {
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+p.pt-000008 {
+ margin-bottom: 6pt;
+ margin-left: 0.50in;
+ text-indent: -0.25in;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000009 {
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000010 {
+ margin: 0 0 0 0.50in;
+ padding: 0 0 0 0;
+}
+a.pt-000011 {
+ text-decoration: none;
+}
+p.pt-ListBullet2 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ margin-left: 0.75in;
+ text-indent: -0.25in;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-000012 {
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+h3.pt-Heading3 {
+ margin-top: 12pt;
+ margin-bottom: 0;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000013 {
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ letter-spacing: 0.5pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-BodyText3 {
+ margin-top: 24pt;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000014 {
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 4.000in;
+}
+p.pt-BodyText3-000015 {
+ margin-top: 1pt;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><h1 dir="ltr" class="pt-Heading1"><span class="pt-DefaultParagraphFont">Team Operating Agreement</span></h1><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000000">Purpose of the Team Operating Agreement (TOA)</span></h2><p dir="ltr" class="pt-BodyText"><span class="pt-DefaultParagraphFont-000001">[Describe the purpose of the document. Depending on the nature of the project or culture of the company, other sections may be added to the document.]</span></p><h6 dir="ltr" class="pt-Heading6"><span class="pt-DefaultParagraphFont-000002">Sample text:</span></h6><p dir="ltr" class="pt-BodyText"><span class="pt-DefaultParagraphFont-000001">This TOA serves as the guidelines and ground rules to help the project team work most productively together over the course of the project. The TOA is a living document and may be updated as the need arises throughout the project. Any updates will be discussed with and ratified by the project team members.</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000000">Team Communications</span></h2><p dir="ltr" class="pt-BodyText"><span class="pt-DefaultParagraphFont-000001">[Describe how project team members will communicate with each other. Include where project documents will be stored and how they may be accessed, how and when meeting agendas and minutes will be distributed, and how confidential information will be handled.]</span></p><h6 dir="ltr" class="pt-Heading6"><span class="pt-DefaultParagraphFont-000002">Sample text:</span></h6><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">The project’s SharePoint site will house the most up-to-date version of project documents. All team members will be given access.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Meeting agendas will be e-mailed to project team members at least 24 hours prior to meetings. Meeting minutes will be posted to the project SharePoint site within 48 hours after meetings.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Team members will appreciate the sensitive nature of information discussed during this project and will share with care. Where applicable, documents will include a footer indicating that information is confidential.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">“Sidebar” conversations between team members during team meetings will not be allowed.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">All communication will be open and courteous. No “overtalking” or interrupting.</span></p><p dir="ltr" class="pt-000005"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Team members will keep each other informed.</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000000">Decision Making</span></h2><p dir="ltr" class="pt-BodyText"><span class="pt-DefaultParagraphFont-000001">[Describe how project team members will make decisions. Everyone must agree on how decisions will be made to ensure that everyone can live with the decisions made and to ensure that the project can move forward. Include guidelines for voting on decisions, how decisions will be documented, definitions of key terms, and what happens if the team cannot come to a decision (for example, escalation to the project sponsor or to a governing body).]</span></p><h6 dir="ltr" class="pt-Heading6"><span class="pt-DefaultParagraphFont-000002">Sample text:</span></h6><p dir="ltr" class="pt-000006"><span class="pt-000007">1.</span><span class="pt-DefaultParagraphFont-000001">Consensus means that everyone can live with the decision. It doesn’t mean everyone has to agree 100%.</span></p><p dir="ltr" class="pt-000008"><span class="pt-000007">2.</span><span class="pt-DefaultParagraphFont-000001">The team will use thumbs up/thumbs down voting to make decisions quickly and move on.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Thumbs up = agree with no further discussion.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Thumbs sideways = agree, but have further questions. (Questions will be asked and answered immediately after the vote.)</span></p><p dir="ltr" class="pt-000005"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Thumbs down = cannot agree to the solution proposed. (Be prepared to answer the question: What would it take for you to go to thumbs sideways or thumbs up?)</span><span class="pt-DefaultParagraphFont-000009"><br />‎<span class="pt-000010" /></span><span class="pt-DefaultParagraphFont-000001">Anyone on the team may call for a vote at any time.</span></p><p dir="ltr" class="pt-000006"><span class="pt-000007">3.</span><span class="pt-DefaultParagraphFont-000001">Members may abstain from voting.</span></p><p dir="ltr" class="pt-000006"><span class="pt-000007">4.</span><span class="pt-DefaultParagraphFont-000001">No decision is made if there are any thumbs-down votes.</span></p><p dir="ltr" class="pt-000008"><span class="pt-000007">5.</span><span class="pt-DefaultParagraphFont-000001">Meeting minutes will document the decisions made. If you have questions after reviewing the minutes, contact the project manager and determine the course of action, such as to bring questions to the team for discussion again.</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000000">Meetings</span></h2><p dir="ltr" class="pt-BodyText"><span class="pt-DefaultParagraphFont-000001">[So much project work and decision-making happens during meetings that it is important to establish how project team meetings will work. Address what will happen at meetings (generally). Establish who will be responsible for the facilitating, frequency, and scheduling of meetings, and attendance expectations.]</span></p><h6 dir="ltr" class="pt-Heading6"><span class="pt-DefaultParagraphFont-000002">Sample text:</span></h6><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Project subteam leaders will report status at each team meeting.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span xml:space="preserve" class="pt-DefaultParagraphFont-000001">Project team members will meet </span><a id="Text19" class="pt-000011"></a><span class="pt-DefaultParagraphFont-000001">[frequency, date, time, and locations of meetings]</span><span class="pt-DefaultParagraphFont-000001">. During each meeting, a “parking lot” will be used to record topics that require discussion at a later date.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Issues, risks, change requests, and action items will be reviewed and updated at each meeting.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">The project manager will be responsible for facilitating and keeping meetings on track. Team members will accept the project manager’s decision to table or “park” a discussion topic.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Meetings will start and end on time. Team members will attend meetings in person when feasible. A dial-in number will be available for remote attendance.</span></p><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Sending “stand ins” to meetings will not be allowed unless approved by the project manager prior to meetings.</span></p><p dir="ltr" class="pt-000005"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">It is the responsibility of each team member to stay current on the project team activities, even when he or she has missed a meeting.</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000000">Personal Courtesies</span></h2><p dir="ltr" class="pt-BodyText"><span class="pt-DefaultParagraphFont-000001">[Outline the personal courtesies that team members will extend to one another. The contents of this section depend largely on the culture of your company. Do not assume that personal behaviors are understood.]</span></p><h6 dir="ltr" class="pt-Heading6"><span class="pt-DefaultParagraphFont-000002">Sample text:</span></h6><p dir="ltr" class="pt-000003"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">Each team member represents a specific area of expertise or business unit. Team members will bring their individual perspectives to the team and will also consider what is best for the company.</span></p><p dir="ltr" class="pt-000005"><span class="pt-000004"></span><span class="pt-DefaultParagraphFont-000001">All cell phones and other communication devices must be silenced during meetings and used on an exception basis only.</span></p><p dir="ltr" class="pt-ListBullet2"><span xml:space="preserve" class="pt-000012"> </span></p><p dir="ltr" class="pt-ListBullet2"><span xml:space="preserve" class="pt-000012"> </span></p><p dir="ltr" class="pt-ListBullet2"><span xml:space="preserve" class="pt-000012"> </span></p><p dir="ltr" class="pt-ListBullet2"><span xml:space="preserve" class="pt-000012"> </span></p><p dir="ltr" class="pt-ListBullet2"><span xml:space="preserve" class="pt-000012"> </span></p><h3 dir="ltr" class="pt-Heading3"><span class="pt-DefaultParagraphFont-000013">Reviewed and approved by:</span></h3><p dir="ltr" class="pt-BodyText3"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span xml:space="preserve" class="pt-DefaultParagraphFont-000014">Project Manager </span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">________________________________________________</span><span class="pt-DefaultParagraphFont-000001">____________________</span></p><p dir="ltr" class="pt-BodyText3-000015"><span class="pt-DefaultParagraphFont-000014">Project Team Member Name</span><span class="pt-DefaultParagraphFont-000001">Date:</span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-05.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-05.html
new file mode 100644
index 0000000..020daab
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-05.html
@@ -0,0 +1,263 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title>C:\Users\Eric\Documents\Open-Xml-PowerTools\OpenXmlPowerToolsExamples\HtmlConverter01\Test-05.docx</title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+tr.pt-000001 {
+ height: 0.19in;
+}
+td.pt-000002 {
+ vertical-align: top;
+ width: 499.5pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal {
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont {
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000003 {
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000004 {
+ height: 5.19in;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 139.5pt;
+ border-top: none;
+ border-right: solid #808080 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Testimonial {
+ margin-top: 10pt;
+ line-height: 200.0%;
+ text-align: right;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000006 {
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Reference {
+ margin-bottom: 46pt;
+ text-align: right;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000007 {
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 360pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #808080 1.0pt;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+h1.pt-Heading1 {
+ margin-bottom: 4pt;
+ margin-left: 0.18in;
+ font-family: 'Garamond', 'serif';
+ font-size: 13pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000009 {
+ font-family: 'Garamond', 'serif';
+ font-size: 13pt;
+ font-style: italic;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Text {
+ margin-bottom: 4pt;
+ margin-left: 0.18in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+}
+p.pt-000010 {
+ margin-bottom: 0;
+ margin-left: 0.68in;
+ text-indent: -0.25in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-000011 {
+ font-family: Symbol;
+ font-size: 6pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.245in;
+}
+span.pt-000012 {
+ font-family: Symbol;
+ font-size: 6pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+p.pt-000013 {
+ margin-bottom: 4pt;
+ margin-left: 0.68in;
+ text-indent: -0.25in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+}
+p.pt-smallspacing {
+ margin-left: 0.25in;
+ font-family: 'Garamond', 'serif';
+ font-size: 4pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000014 {
+ font-size: 4pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Education {
+ margin-left: 0.18in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000015 {
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+h2.pt-Heading2 {
+ margin-top: 8pt;
+ margin-bottom: 4pt;
+ margin-left: 0.18in;
+ font-family: 'Garamond', 'serif';
+ font-size: 11pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000016 {
+ font-family: 'Garamond', 'serif';
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000017 {
+ margin-left: 0.68in;
+ text-indent: -0.25in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-Position {
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Location {
+ margin-left: 0.93in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Affiliation {
+ margin-left: 0.18in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000001"><td colspan="2" class="pt-000002"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont">Diane White</span></p><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont">123 Main St, Seattle, WA 98112</span></p><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont">206-555-1212</span></p><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont">janice@adventureworks.com</span></p><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000004"><td class="pt-000005"><p dir="ltr" class="pt-Testimonial"><span class="pt-DefaultParagraphFont-000006">“...exceptionally energetic and enthusiastic teacher...projects a charisma that captures the imagination of students...demonstrated excellent classroom management skill...”</span></p><p dir="ltr" class="pt-Reference"><span class="pt-DefaultParagraphFont">Simon Pearson</span><span class="pt-DefaultParagraphFont-000007"><br />‎</span><span class="pt-DefaultParagraphFont">former administrator</span></p><p dir="ltr" class="pt-Reference"><span xml:space="preserve" class="pt-000003"> </span></p><p dir="ltr" class="pt-Testimonial"><span class="pt-DefaultParagraphFont-000006">“...business background in technology was supportive to the use of videos and computers in the class...She volunteered for cooperative opportunities in the media center and helped teachers to accommodate computers...I recommend her with the highest regard...”</span></p><p dir="ltr" class="pt-Reference"><span class="pt-DefaultParagraphFont">Aidan Delaney</span><span class="pt-DefaultParagraphFont-000007"><br />‎</span><span class="pt-DefaultParagraphFont">2nd Grade Teacher</span><span class="pt-DefaultParagraphFont-000007"><br />‎</span><span class="pt-DefaultParagraphFont">New York City Schools</span></p><p dir="ltr" class="pt-Reference"><span xml:space="preserve" class="pt-000003"> </span></p><p dir="ltr" class="pt-Testimonial"><span class="pt-DefaultParagraphFont-000006">“...deeply involved in learning about the educational state-of-the-art, investigating research and designing instructional materials...I look forward to the time when Diane will bring her love of children, enthusiasm, initiative, and intelligence into her own classroom.”</span></p><p dir="ltr" class="pt-Reference"><span class="pt-DefaultParagraphFont">Monica Brink, Ed.D.</span></p><p dir="ltr" class="pt-Testimonial"><span class="pt-DefaultParagraphFont-000006">“My ability to motivate students and share a love of learning fosters a successful classroom environment. ...I would welcome becoming part of ‘the village that raises the child’ in your district.”</span></p><p dir="ltr" class="pt-Reference"><span class="pt-DefaultParagraphFont">Jenny Lysaker</span></p><p dir="ltr" class="pt-Reference"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span class="pt-DefaultParagraphFont-000009">Professional Profile</span></h1><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Eager to bring elementary students into the twenty-first century using a unique combination of education experience coupled with ten years’ business background in computer systems management.</span></p><p dir="ltr" class="pt-000010"><span class="pt-000011"></span><span class="pt-DefaultParagraphFont">Hold Masters Degree in Elementary Education and Bachelors Degree in Computer Science.</span></p><p dir="ltr" class="pt-000010"><span class="pt-000012"></span><span class="pt-DefaultParagraphFont">Experienced in use of the Internet and educational software.</span></p><p dir="ltr" class="pt-000013"><span class="pt-000012"></span><span class="pt-DefaultParagraphFont">Dedicated to enthusiastic and dynamic teaching as a means of creating and nurturing a lifelong love of knowledge in children.</span></p><p dir="ltr" class="pt-smallspacing"><span xml:space="preserve" class="pt-000014"> </span></p><h1 dir="ltr" class="pt-Heading1"><span class="pt-DefaultParagraphFont-000009">Education, Honors, and Certifications</span></h1><p dir="ltr" class="pt-Education"><span class="pt-DefaultParagraphFont-000015">M.S. Elementary Education</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Elm College, Flushing, NY. 1995</span></p><p dir="ltr" class="pt-Education"><span class="pt-DefaultParagraphFont-000015">Bachelor of Science Computer Science</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Fir Tree University, Hempstead, NY. 1984</span></p><p dir="ltr" class="pt-Education"><span class="pt-DefaultParagraphFont-000015">Kappa Delta Pi Honor Society Member</span></p><p dir="ltr" class="pt-smallspacing"><span xml:space="preserve" class="pt-000014"> </span></p><p dir="ltr" class="pt-Education"><span class="pt-DefaultParagraphFont-000015">Provisional Certifications</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">NY State Elementary Education. 1995</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">NY State Business Education. 1995</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000016">Key Qualifications</span></h2><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Certified in Elementary (K-6) and Business Education</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Plan and instruct each subject area using wide variety of teaching aids, motivational and implementation strategies to engage students in active learning.</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Incorporate learning modality principles into classroom and individual instruction. Develop and conduct inter-grade activities. Utilize Heath automated math management system.</span></p><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Implement technological approaches to subject material. Research educational resources on the Internet. Assist with information retrieval.</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000016">Experienced Computer Educator</span></h2><p dir="ltr" class="pt-Text"><span class="pt-DefaultParagraphFont">Designed and conducted various faculty and student workshops for training in word processing and spreadsheet software. Instructed corporate personnel in use of word processing, desktop publishing, and drafting programs for conversion from manual typesetting and drafting to computer assisted methods.</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000016">Computer Skills</span></h2><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Software (</span><span class="pt-Position">IBM</span><span xml:space="preserve" class="pt-Position"> and MAC environments):</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> Microsoft Windows® and DOS, </span><span class="pt-DefaultParagraphFont">WordPerfect</span><span class="pt-DefaultParagraphFont">, Lotus123, Microsoft Word, PageMaker, AutoCAD, Books in Print, Baker & Taylor Links, Bibbase</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span xml:space="preserve" class="pt-DefaultParagraphFont">Working knowledge of the </span><span class="pt-Position">Internet</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">System installations and debugging;</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> terminal/printer operations</span></p><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p><h1 dir="ltr" class="pt-Heading1"><span class="pt-DefaultParagraphFont-000009">Employment</span></h1><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000016">Professional Development in Education</span></h2><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Substitute Teacher, K thru High School,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> April 1995 to present</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Graduate Advisor, Education Dept.,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> October 1995 to present</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Elm College, Flushing, NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Workshop Presenter,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> November 1995</span></p><p dir="ltr" class="pt-Location"><span xml:space="preserve" class="pt-DefaultParagraphFont">First combined International Reading Association Regional Conference, </span><span class="pt-DefaultParagraphFont">Nashville</span><span xml:space="preserve" class="pt-DefaultParagraphFont">, </span><span class="pt-DefaultParagraphFont">TN</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Information Services Assistant,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> May 1994 to August 1995</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Elm College, Flushing, NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Student Teacher,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> September to December 1994</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Fir Tree Elementary, Flushing, NY</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000016">Computer Related Training Positions</span></h2><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Workshop Presenter,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> February, 1995</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Maple High School, East Islip, NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Graduate Assistant,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> August 1993 to May 1994</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Elm College, Flushing, NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Software Engineer,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> 1989 to 1991</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Trey Research, Smithtown, NY</span></p><h2 dir="ltr" class="pt-Heading2"><span class="pt-DefaultParagraphFont-000016">Corporate Computer Systems Management</span></h2><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Systems Manager,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> 1987 to 1989</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">A. Datum Corporation, Bohemia, NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Software Quality Assurance Engineer,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> 1986 to 1987</span></p><p dir="ltr" class="pt-Location"><span xml:space="preserve" class="pt-DefaultParagraphFont">Fabrikam, Inc., </span><span class="pt-DefaultParagraphFont">Smithtown</span><span xml:space="preserve" class="pt-DefaultParagraphFont">, </span><span class="pt-DefaultParagraphFont">NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Staff Administrator, Executive Department,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> 1984 to 1986</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">The Telephone Company, Brooklyn, NY</span></p><p dir="ltr" class="pt-000017"><span class="pt-000012"></span><span class="pt-Position">Student Director/Assistant, Computer Science Lab,</span><span xml:space="preserve" class="pt-DefaultParagraphFont"> 1981 to 1984</span></p><p dir="ltr" class="pt-Location"><span class="pt-DefaultParagraphFont">Fir Tree University, Hempstead, NY</span></p><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p><h1 dir="ltr" class="pt-Heading1"><span class="pt-DefaultParagraphFont-000009">Professional Affiliations</span></h1><p dir="ltr" class="pt-Affiliation"><span class="pt-DefaultParagraphFont">International Reading Association</span></p><p dir="ltr" class="pt-Affiliation"><span class="pt-DefaultParagraphFont">Association for Supervision and Curriculum Development</span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-06.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-06.html
new file mode 100644
index 0000000..be03f63
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-06.html
@@ -0,0 +1,329 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000001 {
+ vertical-align: top;
+ width: 14.8%;
+ border-top: solid #BCB8AC 1.0pt;
+ padding-top: 0;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ background: #F24F4F;
+ padding-bottom: 0;
+}
+p.pt-Normal {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ font-family: Century Gothic;
+ font-size: 10pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000002 {
+ color: #4C483D;
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000003 {
+ vertical-align: top;
+ width: 10.7%;
+ border-top: solid #BCB8AC 1.0pt;
+ padding-top: 0;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ background: #F24F4F;
+ padding-bottom: 0;
+}
+p.pt-Normal-000004 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ text-align: center;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #FFFFFF;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 10.6%;
+ border-top: solid #BCB8AC 1.0pt;
+ padding-top: 0;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ background: #F24F4F;
+ padding-bottom: 0;
+}
+td.pt-000006 {
+ vertical-align: top;
+ width: 14.8%;
+ border-top: none;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal-000007 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000008 {
+ color: #4C483D;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000009 {
+ vertical-align: top;
+ width: 10.7%;
+ border-top: none;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal-000010 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ margin-left: 0.27in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000011 {
+ color: #4C483D;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000012 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ margin-left: 0.31in;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+p.pt-Normal-000013 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+td.pt-000014 {
+ vertical-align: top;
+ width: 10.6%;
+ border-top: none;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000015 {
+ vertical-align: top;
+ width: 14.8%;
+ border-top: none;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ background: #DDDBD5;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000016 {
+ vertical-align: top;
+ width: 10.7%;
+ border-top: none;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ background: #DDDBD5;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000017 {
+ vertical-align: top;
+ width: 10.6%;
+ border-top: none;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+ background: #DDDBD5;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal-000018 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ margin-left: 0.15in;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000019 {
+ color: #4C483D;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000020 {
+ color: #4C483D;
+ font-family: 'Garamond', 'serif';
+ font-size: 10pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000021 {
+ color: #4C483D;
+ font-size: 10pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000022 {
+ vertical-align: top;
+ width: 14.8%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #BCB8AC 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+}
+span.pt-DefaultParagraphFont-000023 {
+ color: #F24F4F;
+ font-family: Century Gothic;
+ font-size: 8pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000024 {
+ vertical-align: top;
+ width: 10.7%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #BCB8AC 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+}
+p.pt-Normal-000025 {
+ margin-top: 3pt;
+ margin-bottom: 3pt;
+ margin-left: 0.27in;
+ font-family: Century Gothic;
+ font-size: 10pt;
+ line-height: 108%;
+ margin-right: 0;
+}
+span.pt-000026 {
+ color: #4C483D;
+ font-size: 10pt;
+ text-transform: uppercase;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000027 {
+ vertical-align: top;
+ width: 10.6%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right: solid #BCB8AC 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #BCB8AC 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #BCB8AC 1.0pt;
+ padding-left: 5.4pt;
+}
+h2.pt-Heading2 {
+ margin-top: 6pt;
+ margin-bottom: 6pt;
+ font-family: 'Garamond', 'serif';
+ font-size: 13pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000028 {
+ color: #4C483D;
+ font-size: 13pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><div align="left"><table dir="ltr" class="pt-000000"><tr><td class="pt-000001"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 1</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 2</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 3</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 4</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 5</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 6</span></p></td><td class="pt-000003"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 7</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal-000004"><span class="pt-DefaultParagraphFont">Month 8</span></p></td></tr><tr><td class="pt-000006"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000008">Starting cash</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$20000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$22000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000015"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000008">Cash In:</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000010"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000017"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000006"><p dir="ltr" class="pt-Normal-000018"><span class="pt-DefaultParagraphFont-000008">Cash Sales Paid</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$2000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$2000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000015"><p dir="ltr" class="pt-Normal-000018"><span class="pt-DefaultParagraphFont-000008">Receivables</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$4000.00</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$4000.00</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000017"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000006"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000019">Total Cash In</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000020">$6000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000020">$6000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td></tr><tr><td class="pt-000015"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000008">Cash Out:</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000010"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000012"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000017"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000006"><p dir="ltr" class="pt-Normal-000018"><span class="pt-DefaultParagraphFont-000008">Rent</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$1000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$1000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000015"><p dir="ltr" class="pt-Normal-000018"><span class="pt-DefaultParagraphFont-000008">Payroll</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$1000.00</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$1000.00</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000017"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000006"><p dir="ltr" class="pt-Normal-000018"><span class="pt-DefaultParagraphFont-000008">Other</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$2000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$2000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000015"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000019">Total Cash Out</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000020">$4000.00</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000020">$4000.00</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td><td class="pt-000017"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000021"> </span></p></td></tr><tr><td class="pt-000006"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000008">Ending Balance</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000010"><span class="pt-DefaultParagraphFont-000011">$22000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000012"><span class="pt-DefaultParagraphFont-000011">$24000.00</span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000009"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td><td class="pt-000014"><p dir="ltr" class="pt-Normal-000013"><span xml:space="preserve" class="pt-000002"> </span></p></td></tr><tr><td class="pt-000022"><p dir="ltr" class="pt-Normal-000007"><span class="pt-DefaultParagraphFont-000023">Change (cash flow)</span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal-000025"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000024"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td><td class="pt-000027"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000026"> </span></p></td></tr></table></div><h2 dir="ltr" class="pt-Heading2"><span xml:space="preserve" class="pt-000028"> </span></h2></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-07.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-07.html
new file mode 100644
index 0000000..50f1ede
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-07.html
@@ -0,0 +1,166 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title>ארצות הברית – ויקיפדיה</title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+h1.pt-Heading1 {
+ margin-top: 5pt;
+ margin-bottom: 5pt;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 24pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #FF0000;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 24pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal {
+ background: #FFC000;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000000 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-NormalWeb {
+ margin-top: 5pt;
+ margin-bottom: 5pt;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000001 {
+ color: #4472C4;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ text-decoration: underline;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000002 {
+ color: #4472C4;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000003 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000004 {
+ color: #92D050;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+h2.pt-Heading2 {
+ margin-top: 5pt;
+ margin-bottom: 5pt;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 18pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000005 {
+ color: #FF0000;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 18pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000006 {
+ margin-top: 5pt;
+ margin-bottom: 0;
+ margin-right: 0.25in;
+ text-indent: -0.25in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-left: 0;
+}
+span.pt-000007 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+p.pt-000008 {
+ margin-top: 5pt;
+ margin-bottom: 0;
+ margin-right: 0.75in;
+ text-indent: -0.38in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-left: 0;
+}
+span.pt-000009 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.375in;
+}
+p.pt-000010 {
+ margin-top: 5pt;
+ margin-bottom: 0;
+ margin-right: 0.85in;
+ text-indent: -0.35in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-left: 0;
+}
+span.pt-000011 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.350in;
+}
+p.pt-000012 {
+ margin-top: 5pt;
+ margin-bottom: 5pt;
+ margin-right: 0.25in;
+ text-indent: -0.25in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-left: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><h1 dir="rtl" class="pt-Heading1">‏<span lang="he-IL" class="pt-DefaultParagraphFont">‏ארצות הברית‏</span></h1><p dir="rtl" class="pt-Normal">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000000">‏המונח "‏</span><span class="pt-DefaultParagraphFont-000000">‎USA‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000000">‏" מפנה לכאן. לערך העוסק בערוץ הטלוויזיה, ראו ‏</span><span class="pt-DefaultParagraphFont-000000">‎USA‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000000">‏ (ערוץ טלוויזיה).‏</span></p><p dir="rtl" class="pt-NormalWeb">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000001">‏ארצות הברית של אמריקה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000002">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏(באנגלית: ‏</span><span class="pt-DefaultParagraphFont-000001">‎United States of America‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏, או בראשי תיבות: ‏</span><span class="pt-DefaultParagraphFont-000001">‎USA‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏, בתרגום מילולי לעברית: "המדינות המאוחדות של אמריקה") היא פדרציה ורפובליקה-חוקתית המורכבת מ-50 מדינות וממחוז פדרלי. המדינה שוכנת ברוּבָּה במרכז יבשת אמריקה הצפונית, שבה נמצאות 49 מהמדינות וכן מחוז קולומביה שבו נמצאת העיר וושינגטון, בירת המדינה. ארצות הברית גובלת בקנדה בצפון ובמקסיקו בדרום. מדינת אלסקה שוכנת בצפון-מערב היבשת, כאשר קנדה נמצאת מזרחית לה ורוסיה נמצאת בצד מערב, מעבר למצר ברינג. המדינה ה- 50 היא הוואי, ארכיפלג השוכן באוקיינוס השקט.‏</span></p><p dir="rtl" class="pt-NormalWeb">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏שטחה של ארצות הברית הוא 9.83 מיליון קמ"ר, ואוכלוסייתה, נכון ליולי 2013 מונה כ-317,181,000 מיליון תושבים. ארצות הברית היא אחת מהמדינות המגוונות ביותר מבחינה אתנית, שנוצר כתוצאה מהגירה רחבת היקף ממדינות רבות. כלכלת ארצות הברית היא הכלכלה הלאומית הגדולה ביותר בעולם, והתוצר המקומי הגולמי שלה היה למעלה מ-14 טריליון דולר בשנת 2009.‏</span></p><p dir="rtl" class="pt-NormalWeb">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000004">‏ארצות הברית נחשבת כיום - לאחר התפרקותה של ברית המועצות - למעצמת העל היחידה בעולם, והיא בעלת חשיבות מכרעת בכלכלה, בביטחון וביחסי החוץ הבינלאומיים.‏</span></p><h2 dir="rtl" class="pt-Heading2">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תוכן עניינים‏</span></h2><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏אטימולוגיה‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏2.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏היסטוריה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏ ‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏2.1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏התקופה הקדם קולוניאלית וראשית הקולוניזציה‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏2.2.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏השגת עצמאות וההתפשטות מערבה‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏2.3.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏מלחמת האזרחים והתפתחות התעשייה‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏2.4.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏מלחמת העולם הראשונה, השפל הגדול ומלחמת העולם השנייה‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏2.5.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏המלחמה הקרה וזכויות האזרח‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏2.6.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏מתום המלחמה הקרה ועד היום‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏3.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏פוליטיקה וממשל‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏4.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏כלכלה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏ ‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏4.1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏הכנסה‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏5.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏שלטון מקומי‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏6.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏גאוגרפיה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏ ‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏6.1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏פני הארץ‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏6.2.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏אקלים‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏6.3.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏נהרות‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏6.4.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏פאונה ופלורה‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏6.5.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏דינוזאורים‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏7.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏דמוגרפיה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏ ‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏7.1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏דתות‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏ ‏</span></p><p dir="rtl" class="pt-000010">‏<span lang="he-IL" class="pt-000011">‏7.1.1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏יהדות ארצות הברית לפי מדינות‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏8.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏ערים‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏9.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏חינוך‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏10.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏צבא וביטחון‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏11.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏תרבות‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000003">‏ ‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏11.1.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏ספרות, פילוסופיה ואמנות‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏11.2.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏קולינריה‏</span></p><p dir="rtl" class="pt-000008">‏<span lang="he-IL" class="pt-000009">‏11.3.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏ספורט‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏12.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏ראו גם‏</span></p><p dir="rtl" class="pt-000006">‏<span lang="he-IL" class="pt-000007">‏13.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏קישורים חיצוניים‏</span></p><p dir="rtl" class="pt-000012">‏<span lang="he-IL" class="pt-000007">‏14.‏</span><span lang="ar-SA" class="pt-DefaultParagraphFont-000003">‏הערות שוליים‏</span></p></div></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-08.html b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-08.html
new file mode 100644
index 0000000..49c5f86
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter01/Test-08.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div /></body></html>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/HtmlToWmlConverter02.cs b/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/HtmlToWmlConverter02.cs
new file mode 100644
index 0000000..53436c2
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/HtmlToWmlConverter02.cs
@@ -0,0 +1,359 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using OpenXmlPowerTools.HtmlToWml;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ FileInfo templateDoc = new FileInfo("../../TemplateDocument.docx");
+ FileInfo dataFile = new FileInfo(Path.Combine(tempDi.FullName, "Data.xml"));
+
+ // The following method generates a large data file with random data.
+ // In a real world scenario, this is where you would query your data source and produce XML that will drive your document generation process.
+ int numberOfDocumentsToGenerate = 100;
+ XElement data = GenerateDataFromDataSource(dataFile, numberOfDocumentsToGenerate);
+
+ WmlDocument wmlDoc = new WmlDocument(templateDoc.FullName);
+ int count = 1;
+ foreach (var customer in data.Elements("Customer"))
+ {
+ FileInfo assembledDoc = new FileInfo(Path.Combine(tempDi.FullName, string.Format("Letter-{0:0000}.docx", count++)));
+ Console.WriteLine("Generating {0}", assembledDoc.Name);
+ bool templateError;
+ WmlDocument wmlAssembledDoc = DocumentAssembler.AssembleDocument(wmlDoc, customer, out templateError);
+ if (templateError)
+ {
+ Console.WriteLine("Errors in template.");
+ Console.WriteLine("See {0} to determine the errors in the template.", assembledDoc.Name);
+ }
+ wmlAssembledDoc.SaveAs(assembledDoc.FullName);
+
+ Console.WriteLine("Converting to HTML {0}", assembledDoc.Name);
+ var htmlFileName = ConvertToHtml(assembledDoc.FullName, tempDi.FullName);
+
+ Console.WriteLine("Converting back to DOCX {0}", htmlFileName.Name);
+ ConvertToDocx(htmlFileName.FullName, tempDi.FullName);
+ }
+ }
+
+ private static string[] s_productNames = new[] {
+ "Unicycle",
+ "Bicycle",
+ "Tricycle",
+ "Skateboard",
+ "Roller Blades",
+ "Hang Glider",
+ };
+
+ private static XElement GenerateDataFromDataSource(FileInfo dataFi, int numberOfDocumentsToGenerate)
+ {
+ var customers = new XElement("Customers");
+ Random r = new Random();
+ for (int i = 0; i < numberOfDocumentsToGenerate; ++i)
+ {
+ var customer = new XElement("Customer",
+ new XElement("CustomerID", i + 1),
+ new XElement("Name", "Eric White"),
+ new XElement("HighValueCustomer", r.Next(2) == 0 ? "True" : "False"),
+ new XElement("Orders"));
+ var orders = customer.Element("Orders");
+ int numberOfOrders = r.Next(10) + 1;
+ for (int j = 0; j < numberOfOrders; j++)
+ {
+ var order = new XElement("Order",
+ new XAttribute("Number", j + 1),
+ new XElement("ProductDescription", s_productNames[r.Next(s_productNames.Length)]),
+ new XElement("Quantity", r.Next(10)),
+ new XElement("OrderDate", "September 26, 2015"));
+ orders.Add(order);
+ }
+ customers.Add(customer);
+ }
+ customers.Save(dataFi.FullName);
+ return customers;
+ }
+
+ public static FileInfo ConvertToHtml(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+
+ var pageTitle = fi.FullName;
+ var part = wDoc.CoreFilePropertiesPart;
+ if (part != null)
+ {
+ pageTitle = (string)part.GetXDocument().Descendants(DC.title).FirstOrDefault() ?? fi.FullName;
+ }
+
+ // TODO: Determine max-width from size of content area.
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
+ {
+ AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ imageFormat = ImageFormat.Png;
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ string imageSource = localDirInfo.Name + "/image" +
+ imageCounter.ToString() + "." + extension;
+
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageSource),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement htmlElement = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Produce HTML document with <!DOCTYPE html > declaration to tell the browser
+ // we are using HTML5.
+ var html = new XDocument(
+ new XDocumentType("html", null, null, null),
+ htmlElement);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+
+ return destFileName;
+ }
+ }
+ }
+
+ private static void ConvertToDocx(string file, string destinationDir)
+ {
+ var sourceHtmlFi = new FileInfo(file);
+ var sourceImageDi = new DirectoryInfo(destinationDir);
+
+ var destDocxFi = new FileInfo(Path.Combine(destinationDir, sourceHtmlFi.Name.Replace(".html", "-ConvertedByHtmlToWml.docx")));
+
+ XElement html = HtmlToWmlReadAsXElement.ReadAsXElement(sourceHtmlFi);
+
+ string usedAuthorCss = HtmlToWmlConverter.CleanUpCss((string)html.Descendants().FirstOrDefault(d => d.Name.LocalName.ToLower() == "style"));
+
+ HtmlToWmlConverterSettings settings = HtmlToWmlConverter.GetDefaultSettings();
+ // image references in HTML files contain the path to the subdir that contains the images, so base URI is the name of the directory
+ // that contains the HTML files
+ settings.BaseUriForImages = sourceHtmlFi.DirectoryName;
+
+ WmlDocument doc = HtmlToWmlConverter.ConvertHtmlToWml(defaultCss, usedAuthorCss, userCss, html, settings);
+ doc.SaveAs(destDocxFi.FullName);
+ }
+
+ public class HtmlToWmlReadAsXElement
+ {
+ public static XElement ReadAsXElement(FileInfo sourceHtmlFi)
+ {
+ string htmlString = File.ReadAllText(sourceHtmlFi.FullName);
+ XElement html = null;
+ try
+ {
+ html = XElement.Parse(htmlString);
+ }
+#if USE_HTMLAGILITYPACK
+ catch (XmlException)
+ {
+ HtmlDocument hdoc = new HtmlDocument();
+ hdoc.Load(sourceHtmlFi.FullName, Encoding.Default);
+ hdoc.OptionOutputAsXml = true;
+ hdoc.Save(sourceHtmlFi.FullName, Encoding.Default);
+ StringBuilder sb = new StringBuilder(File.ReadAllText(sourceHtmlFi.FullName, Encoding.Default));
+ sb.Replace("&", "&");
+ sb.Replace(" ", "\xA0");
+ sb.Replace(""", "\"");
+ sb.Replace("<", "~lt;");
+ sb.Replace(">", "~gt;");
+ sb.Replace("&#", "~#");
+ sb.Replace("&", "&");
+ sb.Replace("~lt;", "<");
+ sb.Replace("~gt;", ">");
+ sb.Replace("~#", "&#");
+ File.WriteAllText(sourceHtmlFi.FullName, sb.ToString(), Encoding.Default);
+ html = XElement.Parse(sb.ToString());
+ }
+#else
+ catch (XmlException e)
+ {
+ throw e;
+ }
+#endif
+ // HtmlToWmlConverter expects the HTML elements to be in no namespace, so convert all elements to no namespace.
+ html = (XElement)ConvertToNoNamespace(html);
+ return html;
+ }
+
+ private static object ConvertToNoNamespace(XNode node)
+ {
+ XElement element = node as XElement;
+ if (element != null)
+ {
+ return new XElement(element.Name.LocalName,
+ element.Attributes().Where(a => !a.IsNamespaceDeclaration),
+ element.Nodes().Select(n => ConvertToNoNamespace(n)));
+ }
+ return node;
+ }
+ }
+
+ static string defaultCss =
+ @"html, address,
+blockquote,
+body, dd, div,
+dl, dt, fieldset, form,
+frame, frameset,
+h1, h2, h3, h4,
+h5, h6, noframes,
+ol, p, ul, center,
+dir, hr, menu, pre { display: block; unicode-bidi: embed }
+li { display: list-item }
+head { display: none }
+table { display: table }
+tr { display: table-row }
+thead { display: table-header-group }
+tbody { display: table-row-group }
+tfoot { display: table-footer-group }
+col { display: table-column }
+colgroup { display: table-column-group }
+td, th { display: table-cell }
+caption { display: table-caption }
+th { font-weight: bolder; text-align: center }
+caption { text-align: center }
+body { margin: auto; }
+h1 { font-size: 2em; margin: auto; }
+h2 { font-size: 1.5em; margin: auto; }
+h3 { font-size: 1.17em; margin: auto; }
+h4, p,
+blockquote, ul,
+fieldset, form,
+ol, dl, dir,
+menu { margin: auto }
+a { color: blue; }
+h5 { font-size: .83em; margin: auto }
+h6 { font-size: .75em; margin: auto }
+h1, h2, h3, h4,
+h5, h6, b,
+strong { font-weight: bolder }
+blockquote { margin-left: 40px; margin-right: 40px }
+i, cite, em,
+var, address { font-style: italic }
+pre, tt, code,
+kbd, samp { font-family: monospace }
+pre { white-space: pre }
+button, textarea,
+input, select { display: inline-block }
+big { font-size: 1.17em }
+small, sub, sup { font-size: .83em }
+sub { vertical-align: sub }
+sup { vertical-align: super }
+table { border-spacing: 2px; }
+thead, tbody,
+tfoot { vertical-align: middle }
+td, th, tr { vertical-align: inherit }
+s, strike, del { text-decoration: line-through }
+hr { border: 1px inset }
+ol, ul, dir,
+menu, dd { margin-left: 40px }
+ol { list-style-type: decimal }
+ol ul, ul ol,
+ul ul, ol ol { margin-top: 0; margin-bottom: 0 }
+u, ins { text-decoration: underline }
+br:before { content: ""\A""; white-space: pre-line }
+center { text-align: center }
+:link, :visited { text-decoration: underline }
+:focus { outline: thin dotted invert }
+/* Begin bidirectionality settings (do not change) */
+BDO[DIR=""ltr""] { direction: ltr; unicode-bidi: bidi-override }
+BDO[DIR=""rtl""] { direction: rtl; unicode-bidi: bidi-override }
+*[DIR=""ltr""] { direction: ltr; unicode-bidi: embed }
+*[DIR=""rtl""] { direction: rtl; unicode-bidi: embed }
+
+";
+
+ static string userCss = @"";
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/HtmlToWmlConverter02.csproj b/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/HtmlToWmlConverter02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/HtmlToWmlConverter02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/TemplateDocument.docx b/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/TemplateDocument.docx
new file mode 100644
index 0000000..c570f6b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/HtmlToWmlConverter02/TemplateDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ListItemRetriever01/ListItemRetriever01.cs b/OpenXmlPowerToolsExamples/ListItemRetriever01/ListItemRetriever01.cs
new file mode 100644
index 0000000..eb77674
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ListItemRetriever01/ListItemRetriever01.cs
@@ -0,0 +1,151 @@
+/***************************************************************************
+
+Copyright (c) Microsoft Corporation 2014.
+
+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.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class ListItemRetriever01
+{
+ private class XmlStackItem
+ {
+ public XElement Element;
+ public int[] LevelNumbers;
+ }
+
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ using (WordprocessingDocument wDoc =
+ WordprocessingDocument.Open("../../NumberedListTest.docx", false))
+ {
+ int abstractNumId = 0;
+ XElement xml = ConvertDocToXml(wDoc, abstractNumId);
+ Console.WriteLine(xml);
+ xml.Save(Path.Combine(tempDi.FullName, "Out.xml"));
+ }
+ }
+
+ private static XElement ConvertDocToXml(WordprocessingDocument wDoc, int abstractNumId)
+ {
+ XDocument xd = wDoc.MainDocumentPart.GetXDocument();
+
+ // First, call RetrieveListItem so that all paragraphs are initialized with ListItemInfo
+ var firstParagraph = xd.Descendants(W.p).FirstOrDefault();
+ var listItem = ListItemRetriever.RetrieveListItem(wDoc, firstParagraph);
+
+ XElement xml = new XElement("Root");
+ var current = new Stack<XmlStackItem>();
+ current.Push(
+ new XmlStackItem()
+ {
+ Element = xml,
+ LevelNumbers = new int[] { },
+ });
+ foreach (var paragraph in xd.Descendants(W.p))
+ {
+ // The following does not take into account documents that have tracked revisions.
+ // As necessary, call RevisionAccepter.AcceptRevisions before converting to XML.
+ var text = paragraph.Descendants(W.t).Select(t => (string)t).StringConcatenate();
+ ListItemRetriever.ListItemInfo lii =
+ paragraph.Annotation<ListItemRetriever.ListItemInfo>();
+ if (lii.IsListItem && lii.AbstractNumId == abstractNumId)
+ {
+ ListItemRetriever.LevelNumbers levelNums =
+ paragraph.Annotation<ListItemRetriever.LevelNumbers>();
+ if (levelNums.LevelNumbersArray.Length == current.Peek().LevelNumbers.Length)
+ {
+ current.Pop();
+ var levelNumsForThisIndent = levelNums.LevelNumbersArray;
+ string levelText = levelNums
+ .LevelNumbersArray
+ .Select(l => l.ToString() + ".")
+ .StringConcatenate()
+ .TrimEnd('.');
+ var newCurrentElement = new XElement("Indent",
+ new XAttribute("Level", levelText));
+ current.Peek().Element.Add(newCurrentElement);
+ current.Push(
+ new XmlStackItem()
+ {
+ Element = newCurrentElement,
+ LevelNumbers = levelNumsForThisIndent,
+ });
+ current.Peek().Element.Add(new XElement("Heading", text));
+ }
+ else if (levelNums.LevelNumbersArray.Length > current.Peek().LevelNumbers.Length)
+ {
+ for (int i = current.Peek().LevelNumbers.Length;
+ i < levelNums.LevelNumbersArray.Length;
+ i++)
+ {
+ var levelNumsForThisIndent = levelNums
+ .LevelNumbersArray
+ .Take(i + 1)
+ .ToArray();
+ string levelText = levelNums
+ .LevelNumbersArray
+ .Select(l => l.ToString() + ".")
+ .StringConcatenate()
+ .TrimEnd('.');
+ var newCurrentElement = new XElement("Indent",
+ new XAttribute("Level", levelText));
+ current.Peek().Element.Add(newCurrentElement);
+ current.Push(
+ new XmlStackItem()
+ {
+ Element = newCurrentElement,
+ LevelNumbers = levelNumsForThisIndent,
+ });
+ current.Peek().Element.Add(new XElement("Heading", text));
+ }
+ }
+ else if (levelNums.LevelNumbersArray.Length < current.Peek().LevelNumbers.Length)
+ {
+ for (int i = current.Peek().LevelNumbers.Length;
+ i > levelNums.LevelNumbersArray.Length;
+ i--)
+ current.Pop();
+ current.Pop();
+ var levelNumsForThisIndent = levelNums.LevelNumbersArray;
+ string levelText = levelNums
+ .LevelNumbersArray
+ .Select(l => l.ToString() + ".")
+ .StringConcatenate()
+ .TrimEnd('.');
+ var newCurrentElement = new XElement("Indent",
+ new XAttribute("Level", levelText));
+ current.Peek().Element.Add(newCurrentElement);
+ current.Push(
+ new XmlStackItem()
+ {
+ Element = newCurrentElement,
+ LevelNumbers = levelNumsForThisIndent,
+ });
+ current.Peek().Element.Add(new XElement("Heading", text));
+ }
+ }
+ else
+ {
+ current.Peek().Element.Add(new XElement("Paragraph", text));
+ }
+ }
+ return xml;
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/ListItemRetriever01/ListItemRetriever01.csproj b/OpenXmlPowerToolsExamples/ListItemRetriever01/ListItemRetriever01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ListItemRetriever01/ListItemRetriever01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/ListItemRetriever01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/ListItemRetriever01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ListItemRetriever01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/ListItemRetriever01/NumberedListTest.docx b/OpenXmlPowerToolsExamples/ListItemRetriever01/NumberedListTest.docx
new file mode 100644
index 0000000..2ce3157
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ListItemRetriever01/NumberedListTest.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.Designer.cs b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.Designer.cs
new file mode 100644
index 0000000..146f5f6
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.Designer.cs
@@ -0,0 +1,244 @@
+namespace MarkupSimplifierApp
+{
+ partial class Form1
+ {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent()
+ {
+ this.cbRemoveContentControls = new System.Windows.Forms.CheckBox();
+ this.cbRemoveSmartTags = new System.Windows.Forms.CheckBox();
+ this.cbRemoveRsidInfo = new System.Windows.Forms.CheckBox();
+ this.cbRemoveComments = new System.Windows.Forms.CheckBox();
+ this.cbRemoveEndAndFootNotes = new System.Windows.Forms.CheckBox();
+ this.cbReplaceTabsWithSpaces = new System.Windows.Forms.CheckBox();
+ this.cbRemoveFieldCodes = new System.Windows.Forms.CheckBox();
+ this.cbRemovePermissions = new System.Windows.Forms.CheckBox();
+ this.cbRemoveProof = new System.Windows.Forms.CheckBox();
+ this.cbRemoveSoftHyphens = new System.Windows.Forms.CheckBox();
+ this.cbRemoveLastRenderedPageBreak = new System.Windows.Forms.CheckBox();
+ this.cbRemoveBookmarks = new System.Windows.Forms.CheckBox();
+ this.cbRemoveWebHidden = new System.Windows.Forms.CheckBox();
+ this.cbNormalize = new System.Windows.Forms.CheckBox();
+ this.btnApply = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+ //
+ // cbRemoveContentControls
+ //
+ this.cbRemoveContentControls.AutoSize = true;
+ this.cbRemoveContentControls.Location = new System.Drawing.Point(13, 13);
+ this.cbRemoveContentControls.Name = "cbRemoveContentControls";
+ this.cbRemoveContentControls.Size = new System.Drawing.Size(147, 17);
+ this.cbRemoveContentControls.TabIndex = 0;
+ this.cbRemoveContentControls.Text = "Remove Content Controls";
+ this.cbRemoveContentControls.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveSmartTags
+ //
+ this.cbRemoveSmartTags.AutoSize = true;
+ this.cbRemoveSmartTags.Location = new System.Drawing.Point(13, 37);
+ this.cbRemoveSmartTags.Name = "cbRemoveSmartTags";
+ this.cbRemoveSmartTags.Size = new System.Drawing.Size(123, 17);
+ this.cbRemoveSmartTags.TabIndex = 1;
+ this.cbRemoveSmartTags.Text = "Remove Smart Tags";
+ this.cbRemoveSmartTags.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveRsidInfo
+ //
+ this.cbRemoveRsidInfo.AutoSize = true;
+ this.cbRemoveRsidInfo.Location = new System.Drawing.Point(13, 61);
+ this.cbRemoveRsidInfo.Name = "cbRemoveRsidInfo";
+ this.cbRemoveRsidInfo.Size = new System.Drawing.Size(111, 17);
+ this.cbRemoveRsidInfo.TabIndex = 2;
+ this.cbRemoveRsidInfo.Text = "Remove Rsid Info";
+ this.cbRemoveRsidInfo.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveComments
+ //
+ this.cbRemoveComments.AutoSize = true;
+ this.cbRemoveComments.Location = new System.Drawing.Point(13, 85);
+ this.cbRemoveComments.Name = "cbRemoveComments";
+ this.cbRemoveComments.Size = new System.Drawing.Size(118, 17);
+ this.cbRemoveComments.TabIndex = 3;
+ this.cbRemoveComments.Text = "Remove Comments";
+ this.cbRemoveComments.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveEndAndFootNotes
+ //
+ this.cbRemoveEndAndFootNotes.AutoSize = true;
+ this.cbRemoveEndAndFootNotes.Location = new System.Drawing.Point(13, 109);
+ this.cbRemoveEndAndFootNotes.Name = "cbRemoveEndAndFootNotes";
+ this.cbRemoveEndAndFootNotes.Size = new System.Drawing.Size(164, 17);
+ this.cbRemoveEndAndFootNotes.TabIndex = 4;
+ this.cbRemoveEndAndFootNotes.Text = "Remove End and Foot Notes";
+ this.cbRemoveEndAndFootNotes.UseVisualStyleBackColor = true;
+ //
+ // cbReplaceTabsWithSpaces
+ //
+ this.cbReplaceTabsWithSpaces.AutoSize = true;
+ this.cbReplaceTabsWithSpaces.Location = new System.Drawing.Point(13, 133);
+ this.cbReplaceTabsWithSpaces.Name = "cbReplaceTabsWithSpaces";
+ this.cbReplaceTabsWithSpaces.Size = new System.Drawing.Size(154, 17);
+ this.cbReplaceTabsWithSpaces.TabIndex = 5;
+ this.cbReplaceTabsWithSpaces.Text = "Replace Tabs with Spaces";
+ this.cbReplaceTabsWithSpaces.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveFieldCodes
+ //
+ this.cbRemoveFieldCodes.AutoSize = true;
+ this.cbRemoveFieldCodes.Location = new System.Drawing.Point(13, 157);
+ this.cbRemoveFieldCodes.Name = "cbRemoveFieldCodes";
+ this.cbRemoveFieldCodes.Size = new System.Drawing.Size(124, 17);
+ this.cbRemoveFieldCodes.TabIndex = 6;
+ this.cbRemoveFieldCodes.Text = "Remove Field Codes";
+ this.cbRemoveFieldCodes.UseVisualStyleBackColor = true;
+ //
+ // cbRemovePermissions
+ //
+ this.cbRemovePermissions.AutoSize = true;
+ this.cbRemovePermissions.Location = new System.Drawing.Point(13, 181);
+ this.cbRemovePermissions.Name = "cbRemovePermissions";
+ this.cbRemovePermissions.Size = new System.Drawing.Size(124, 17);
+ this.cbRemovePermissions.TabIndex = 7;
+ this.cbRemovePermissions.Text = "Remove Permissions";
+ this.cbRemovePermissions.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveProof
+ //
+ this.cbRemoveProof.AutoSize = true;
+ this.cbRemoveProof.Location = new System.Drawing.Point(13, 205);
+ this.cbRemoveProof.Name = "cbRemoveProof";
+ this.cbRemoveProof.Size = new System.Drawing.Size(94, 17);
+ this.cbRemoveProof.TabIndex = 8;
+ this.cbRemoveProof.Text = "Remove Proof";
+ this.cbRemoveProof.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveSoftHyphens
+ //
+ this.cbRemoveSoftHyphens.AutoSize = true;
+ this.cbRemoveSoftHyphens.Location = new System.Drawing.Point(13, 229);
+ this.cbRemoveSoftHyphens.Name = "cbRemoveSoftHyphens";
+ this.cbRemoveSoftHyphens.Size = new System.Drawing.Size(133, 17);
+ this.cbRemoveSoftHyphens.TabIndex = 9;
+ this.cbRemoveSoftHyphens.Text = "Remove Soft Hyphens";
+ this.cbRemoveSoftHyphens.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveLastRenderedPageBreak
+ //
+ this.cbRemoveLastRenderedPageBreak.AutoSize = true;
+ this.cbRemoveLastRenderedPageBreak.Location = new System.Drawing.Point(13, 253);
+ this.cbRemoveLastRenderedPageBreak.Name = "cbRemoveLastRenderedPageBreak";
+ this.cbRemoveLastRenderedPageBreak.Size = new System.Drawing.Size(198, 17);
+ this.cbRemoveLastRenderedPageBreak.TabIndex = 10;
+ this.cbRemoveLastRenderedPageBreak.Text = "Remove Last Rendered Page Break";
+ this.cbRemoveLastRenderedPageBreak.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveBookmarks
+ //
+ this.cbRemoveBookmarks.AutoSize = true;
+ this.cbRemoveBookmarks.Location = new System.Drawing.Point(13, 277);
+ this.cbRemoveBookmarks.Name = "cbRemoveBookmarks";
+ this.cbRemoveBookmarks.Size = new System.Drawing.Size(122, 17);
+ this.cbRemoveBookmarks.TabIndex = 11;
+ this.cbRemoveBookmarks.Text = "Remove Bookmarks";
+ this.cbRemoveBookmarks.UseVisualStyleBackColor = true;
+ //
+ // cbRemoveWebHidden
+ //
+ this.cbRemoveWebHidden.AutoSize = true;
+ this.cbRemoveWebHidden.Location = new System.Drawing.Point(13, 301);
+ this.cbRemoveWebHidden.Name = "cbRemoveWebHidden";
+ this.cbRemoveWebHidden.Size = new System.Drawing.Size(129, 17);
+ this.cbRemoveWebHidden.TabIndex = 12;
+ this.cbRemoveWebHidden.Text = "Remove Web Hidden";
+ this.cbRemoveWebHidden.UseVisualStyleBackColor = true;
+ //
+ // cbNormalize
+ //
+ this.cbNormalize.AutoSize = true;
+ this.cbNormalize.Location = new System.Drawing.Point(13, 325);
+ this.cbNormalize.Name = "cbNormalize";
+ this.cbNormalize.Size = new System.Drawing.Size(97, 17);
+ this.cbNormalize.TabIndex = 13;
+ this.cbNormalize.Text = "Normalize XML";
+ this.cbNormalize.UseVisualStyleBackColor = true;
+ //
+ // btnApply
+ //
+ this.btnApply.Location = new System.Drawing.Point(256, 301);
+ this.btnApply.Name = "btnApply";
+ this.btnApply.Size = new System.Drawing.Size(103, 41);
+ this.btnApply.TabIndex = 14;
+ this.btnApply.Text = "Apply to Documents";
+ this.btnApply.UseVisualStyleBackColor = true;
+ this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(378, 354);
+ this.Controls.Add(this.btnApply);
+ this.Controls.Add(this.cbNormalize);
+ this.Controls.Add(this.cbRemoveWebHidden);
+ this.Controls.Add(this.cbRemoveBookmarks);
+ this.Controls.Add(this.cbRemoveLastRenderedPageBreak);
+ this.Controls.Add(this.cbRemoveSoftHyphens);
+ this.Controls.Add(this.cbRemoveProof);
+ this.Controls.Add(this.cbRemovePermissions);
+ this.Controls.Add(this.cbRemoveFieldCodes);
+ this.Controls.Add(this.cbReplaceTabsWithSpaces);
+ this.Controls.Add(this.cbRemoveEndAndFootNotes);
+ this.Controls.Add(this.cbRemoveComments);
+ this.Controls.Add(this.cbRemoveRsidInfo);
+ this.Controls.Add(this.cbRemoveSmartTags);
+ this.Controls.Add(this.cbRemoveContentControls);
+ this.Name = "Form1";
+ this.Text = "Form1";
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.CheckBox cbRemoveContentControls;
+ private System.Windows.Forms.CheckBox cbRemoveSmartTags;
+ private System.Windows.Forms.CheckBox cbRemoveRsidInfo;
+ private System.Windows.Forms.CheckBox cbRemoveComments;
+ private System.Windows.Forms.CheckBox cbRemoveEndAndFootNotes;
+ private System.Windows.Forms.CheckBox cbReplaceTabsWithSpaces;
+ private System.Windows.Forms.CheckBox cbRemoveFieldCodes;
+ private System.Windows.Forms.CheckBox cbRemovePermissions;
+ private System.Windows.Forms.CheckBox cbRemoveProof;
+ private System.Windows.Forms.CheckBox cbRemoveSoftHyphens;
+ private System.Windows.Forms.CheckBox cbRemoveLastRenderedPageBreak;
+ private System.Windows.Forms.CheckBox cbRemoveBookmarks;
+ private System.Windows.Forms.CheckBox cbRemoveWebHidden;
+ private System.Windows.Forms.CheckBox cbNormalize;
+ private System.Windows.Forms.Button btnApply;
+ }
+}
+
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.cs b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.cs
new file mode 100644
index 0000000..4ad1e60
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace MarkupSimplifierApp
+{
+ public partial class Form1 : Form
+ {
+ public Form1()
+ {
+ InitializeComponent();
+ }
+
+ private void btnApply_Click(object sender, EventArgs e)
+ {
+ OpenFileDialog ofd = new OpenFileDialog();
+ ofd.Multiselect = true;
+ DialogResult dr = ofd.ShowDialog();
+ foreach (var item in ofd.FileNames)
+ {
+ using (WordprocessingDocument doc =
+ WordprocessingDocument.Open(item, true))
+ {
+ SimplifyMarkupSettings settings = new SimplifyMarkupSettings
+ {
+ RemoveContentControls = cbRemoveContentControls.Checked,
+ RemoveSmartTags = cbRemoveSmartTags.Checked,
+ RemoveRsidInfo = cbRemoveRsidInfo.Checked,
+ RemoveComments = cbRemoveComments.Checked,
+ RemoveEndAndFootNotes = cbRemoveEndAndFootNotes.Checked,
+ ReplaceTabsWithSpaces = cbReplaceTabsWithSpaces.Checked,
+ RemoveFieldCodes = cbRemoveFieldCodes.Checked,
+ RemovePermissions = cbRemovePermissions.Checked,
+ RemoveProof = cbRemoveProof.Checked,
+ RemoveSoftHyphens = cbRemoveSoftHyphens.Checked,
+ RemoveLastRenderedPageBreak = cbRemoveLastRenderedPageBreak.Checked,
+ RemoveBookmarks = cbRemoveBookmarks.Checked,
+ RemoveWebHidden = cbRemoveWebHidden.Checked,
+ NormalizeXml = cbNormalize.Checked,
+ };
+ OpenXmlPowerTools.MarkupSimplifier.SimplifyMarkup(doc, settings);
+ }
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.resx b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Form1.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj
new file mode 100644
index 0000000..cc1729a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/MarkupSimplifierApp.csproj
@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Compile Update="Form1.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ <Compile Update="Form1.Designer.cs">
+ <SubType>Form</SubType>
+ </Compile>
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Program.cs b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Program.cs
new file mode 100644
index 0000000..2eda1ce
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace MarkupSimplifierApp
+{
+ static class Program
+ {
+ /// <summary>
+ /// The main entry point for the application.
+ /// </summary>
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Resources.Designer.cs b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..5feca88
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.34209
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MarkupSimplifierApp.Properties {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MarkupSimplifierApp.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Resources.resx b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Settings.Designer.cs b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..a8100ea
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MarkupSimplifierApp/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18033
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace MarkupSimplifierApp.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/ContentControls.docx b/OpenXmlPowerToolsExamples/MetricsGetter01/ContentControls.docx
new file mode 100644
index 0000000..4d00eed
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/ContentControls.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/MetricsGetter01.cs b/OpenXmlPowerToolsExamples/MetricsGetter01/MetricsGetter01.cs
new file mode 100644
index 0000000..10f1e47
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/MetricsGetter01.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class MetricsGetter01
+ {
+ static void Main(string[] args)
+ {
+ MetricsGetterSettings settings = null;
+ FileInfo fi = null;
+
+ fi = new FileInfo("../../ContentControls.docx");
+ settings = new MetricsGetterSettings();
+ settings.IncludeTextInContentControls = false;
+ Console.WriteLine("============== No text from content controls ==============");
+ Console.WriteLine(fi.FullName);
+ Console.WriteLine(MetricsGetter.GetMetrics(fi.FullName, settings));
+ Console.WriteLine();
+
+ fi = new FileInfo("../../ContentControls.docx");
+ settings = new MetricsGetterSettings();
+ settings.IncludeTextInContentControls = true;
+ Console.WriteLine("============== With text from content controls ==============");
+ Console.WriteLine(fi.FullName);
+ Console.WriteLine(MetricsGetter.GetMetrics(fi.FullName, settings));
+ Console.WriteLine();
+
+ fi = new FileInfo("../../TrackedRevisions.docx");
+ settings = new MetricsGetterSettings();
+ settings.IncludeTextInContentControls = true;
+ Console.WriteLine("============== Tracked Revisions ==============");
+ Console.WriteLine(fi.FullName);
+ Console.WriteLine(MetricsGetter.GetMetrics(fi.FullName, settings));
+ Console.WriteLine();
+
+ fi = new FileInfo("../../Styles.docx");
+ settings = new MetricsGetterSettings();
+ settings.IncludeTextInContentControls = false;
+ Console.WriteLine("============== Style Hierarchy ==============");
+ Console.WriteLine(fi.FullName);
+ Console.WriteLine(MetricsGetter.GetMetrics(fi.FullName, settings));
+ Console.WriteLine();
+
+ fi = new FileInfo("../../Tables.xlsx");
+ settings = new MetricsGetterSettings();
+ settings.IncludeTextInContentControls = false;
+ settings.IncludeXlsxTableCellData = true;
+ Console.WriteLine("============== Spreadsheet Tables ==============");
+ Console.WriteLine(fi.FullName);
+ Console.WriteLine(MetricsGetter.GetMetrics(fi.FullName, settings));
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/MetricsGetter01.csproj b/OpenXmlPowerToolsExamples/MetricsGetter01/MetricsGetter01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/MetricsGetter01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/MetricsGetter01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/Styles.docx b/OpenXmlPowerToolsExamples/MetricsGetter01/Styles.docx
new file mode 100644
index 0000000..5137186
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/Styles.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/Tables.xlsx b/OpenXmlPowerToolsExamples/MetricsGetter01/Tables.xlsx
new file mode 100644
index 0000000..f4a36cd
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/Tables.xlsx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/MetricsGetter01/TrackedRevisions.docx b/OpenXmlPowerToolsExamples/MetricsGetter01/TrackedRevisions.docx
new file mode 100644
index 0000000..b563d17
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/MetricsGetter01/TrackedRevisions.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/OpenXmlRegex01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex01/OpenXmlRegex01.cs b/OpenXmlPowerToolsExamples/OpenXmlRegex01/OpenXmlRegex01.cs
new file mode 100644
index 0000000..84d885c
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex01/OpenXmlRegex01.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlRegex01
+{
+ public class OpenXmlRegexExample
+ {
+ public static void Main(string[] args)
+ {
+ DateTime n = DateTime.Now;
+ var tempDi = new DirectoryInfo(
+ $"ExampleOutput-{n.Year - 2000:00}-{n.Month:00}-{n.Day:00}-{n.Hour:00}{n.Minute:00}{n.Second:00}");
+ tempDi.Create();
+
+ var sourceDoc = new FileInfo("../../TestDocument.docx");
+ var newDoc = new FileInfo(Path.Combine(tempDi.FullName, "Modified.docx"));
+ File.Copy(sourceDoc.FullName, newDoc.FullName);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(newDoc.FullName, true))
+ {
+ XDocument xDoc = wDoc.MainDocumentPart.GetXDocument();
+
+ // Match content (paragraph 1)
+ IEnumerable<XElement> content = xDoc.Descendants(W.p).Take(1);
+ var regex = new Regex("Video");
+ int count = OpenXmlRegex.Match(content, regex);
+ Console.WriteLine("Example #1 Count: {0}", count);
+
+ // Match content, case insensitive (paragraph 1)
+ content = xDoc.Descendants(W.p).Take(1);
+ regex = new Regex("video", RegexOptions.IgnoreCase);
+ count = OpenXmlRegex.Match(content, regex);
+ Console.WriteLine("Example #2 Count: {0}", count);
+
+ // Match content, with callback (paragraph 1)
+ content = xDoc.Descendants(W.p).Take(1);
+ regex = new Regex("video", RegexOptions.IgnoreCase);
+ OpenXmlRegex.Match(content, regex, (element, match) =>
+ Console.WriteLine("Example #3 Found value: >{0}<", match.Value));
+
+ // Replace content, beginning of paragraph (paragraph 2)
+ content = xDoc.Descendants(W.p).Skip(1).Take(1);
+ regex = new Regex("^Video provides");
+ count = OpenXmlRegex.Replace(content, regex, "Audio gives", null);
+ Console.WriteLine("Example #4 Replaced: {0}", count);
+
+ // Replace content, middle of paragraph (paragraph 3)
+ content = xDoc.Descendants(W.p).Skip(2).Take(1);
+ regex = new Regex("powerful");
+ count = OpenXmlRegex.Replace(content, regex, "good", null);
+ Console.WriteLine("Example #5 Replaced: {0}", count);
+
+ // Replace content, end of paragraph (paragraph 4)
+ content = xDoc.Descendants(W.p).Skip(3).Take(1);
+ regex = new Regex(" [a-z.]*$");
+ count = OpenXmlRegex.Replace(content, regex, " super good point!", null);
+ Console.WriteLine("Example #6 Replaced: {0}", count);
+
+ // Delete content, beginning of paragraph (paragraph 5)
+ content = xDoc.Descendants(W.p).Skip(4).Take(1);
+ regex = new Regex("^Video provides");
+ count = OpenXmlRegex.Replace(content, regex, "", null);
+ Console.WriteLine("Example #7 Deleted: {0}", count);
+
+ // Delete content, middle of paragraph (paragraph 6)
+ content = xDoc.Descendants(W.p).Skip(5).Take(1);
+ regex = new Regex("powerful ");
+ count = OpenXmlRegex.Replace(content, regex, "", null);
+ Console.WriteLine("Example #8 Deleted: {0}", count);
+
+ // Delete content, end of paragraph (paragraph 7)
+ content = xDoc.Descendants(W.p).Skip(6).Take(1);
+ regex = new Regex("[.]$");
+ count = OpenXmlRegex.Replace(content, regex, "", null);
+ Console.WriteLine("Example #9 Deleted: {0}", count);
+
+ // Replace content in inserted text, same author (paragraph 8)
+ content = xDoc.Descendants(W.p).Skip(7).Take(1);
+ regex = new Regex("Video");
+ count = OpenXmlRegex.Replace(content, regex, "Audio", null, true, "Eric White");
+ Console.WriteLine("Example #10 Deleted: {0}", count);
+
+ // Delete content in inserted text, same author (paragraph 9)
+ content = xDoc.Descendants(W.p).Skip(8).Take(1);
+ regex = new Regex("powerful ");
+ count = OpenXmlRegex.Replace(content, regex, "", null, true, "Eric White");
+ Console.WriteLine("Example #11 Deleted: {0}", count);
+
+ // Replace content partially in inserted text, same author (paragraph 10)
+ content = xDoc.Descendants(W.p).Skip(9).Take(1);
+ regex = new Regex("Video provides ");
+ count = OpenXmlRegex.Replace(content, regex, "Audio gives ", null, true, "Eric White");
+ Console.WriteLine("Example #12 Replaced: {0}", count);
+
+ // Delete content partially in inserted text, same author (paragraph 11)
+ content = xDoc.Descendants(W.p).Skip(10).Take(1);
+ regex = new Regex(" to help you prove your point");
+ count = OpenXmlRegex.Replace(content, regex, "", null, true, "Eric White");
+ Console.WriteLine("Example #13 Deleted: {0}", count);
+
+ // Replace content in inserted text, different author (paragraph 12)
+ content = xDoc.Descendants(W.p).Skip(11).Take(1);
+ regex = new Regex("Video");
+ count = OpenXmlRegex.Replace(content, regex, "Audio", null, true, "John Doe");
+ Console.WriteLine("Example #14 Deleted: {0}", count);
+
+ // Delete content in inserted text, different author (paragraph 13)
+ content = xDoc.Descendants(W.p).Skip(12).Take(1);
+ regex = new Regex("powerful ");
+ count = OpenXmlRegex.Replace(content, regex, "", null, true, "John Doe");
+ Console.WriteLine("Example #15 Deleted: {0}", count);
+
+ // Replace content partially in inserted text, different author (paragraph 14)
+ content = xDoc.Descendants(W.p).Skip(13).Take(1);
+ regex = new Regex("Video provides ");
+ count = OpenXmlRegex.Replace(content, regex, "Audio gives ", null, true, "John Doe");
+ Console.WriteLine("Example #16 Replaced: {0}", count);
+
+ // Delete content partially in inserted text, different author (paragraph 15)
+ content = xDoc.Descendants(W.p).Skip(14).Take(1);
+ regex = new Regex(" to help you prove your point");
+ count = OpenXmlRegex.Replace(content, regex, "", null, true, "John Doe");
+ Console.WriteLine("Example #17 Deleted: {0}", count);
+
+ const string leftDoubleQuotationMarks = @"[\u0022“„«»”]";
+ const string words = @"[\w\-&/]+(?:\s[\w\-&/]+)*";
+ const string rightDoubleQuotationMarks = @"[\u0022”‟»«“]";
+
+ // Replace content using replacement pattern (paragraph 16)
+ content = xDoc.Descendants(W.p).Skip(15).Take(1);
+ regex = new Regex($"{leftDoubleQuotationMarks}(?<words>{words}){rightDoubleQuotationMarks}");
+ count = OpenXmlRegex.Replace(content, regex, "‘${words}’", null);
+ Console.WriteLine("Example #18 Replaced: {0}", count);
+
+ // Replace content using replacement pattern in partially inserted text (paragraph 17)
+ content = xDoc.Descendants(W.p).Skip(16).Take(1);
+ regex = new Regex($"{leftDoubleQuotationMarks}(?<words>{words}){rightDoubleQuotationMarks}");
+ count = OpenXmlRegex.Replace(content, regex, "‘${words}’", null, true, "John Doe");
+ Console.WriteLine("Example #19 Replaced: {0}", count);
+
+ // Replace content using replacement pattern (paragraph 18)
+ content = xDoc.Descendants(W.p).Skip(17).Take(1);
+ regex = new Regex($"({leftDoubleQuotationMarks})(video)({rightDoubleQuotationMarks})");
+ count = OpenXmlRegex.Replace(content, regex, "$1audio$3", null, true, "John Doe");
+ Console.WriteLine("Example #20 Replaced: {0}", count);
+
+ // Recognize tabs (paragraph 19)
+ content = xDoc.Descendants(W.p).Skip(18).Take(1);
+ regex = new Regex(@"([1-9])\.\t");
+ count = OpenXmlRegex.Replace(content, regex, "($1)\t", null);
+ Console.WriteLine("Example #21 Replaced: {0}", count);
+
+ // The next two examples deal with line breaks, i.e., the <w:br/> elements.
+ // Note that you should use the U+000D (Carriage Return) character (i.e., '\r')
+ // to match a <w:br/> (or <w:cr/>) and replace content with a <w:br/> element.
+ // Depending on your platform, the end of line character(s) provided by
+ // Environment.NewLine might be "\n" (Unix), "\r\n" (Windows), or "\r" (Mac).
+
+ // Recognize tabs and insert line breaks (paragraph 20).
+ content = xDoc.Descendants(W.p).Skip(19).Take(1);
+ regex = new Regex($@"([1-9])\.{UnicodeMapper.HorizontalTabulation}");
+ count = OpenXmlRegex.Replace(content, regex, $"Article $1{UnicodeMapper.CarriageReturn}", null);
+ Console.WriteLine("Example #22 Replaced: {0}", count);
+
+ // Recognize and remove line breaks (paragraph 21)
+ content = xDoc.Descendants(W.p).Skip(20).Take(1);
+ regex = new Regex($"{UnicodeMapper.CarriageReturn}");
+ count = OpenXmlRegex.Replace(content, regex, " ", null);
+ Console.WriteLine("Example #23 Replaced: {0}", count);
+
+ // Remove soft hyphens (paragraph 22)
+ List<XElement> paras = xDoc.Descendants(W.p).Skip(21).Take(1).ToList();
+ count = OpenXmlRegex.Replace(paras, new Regex($"{UnicodeMapper.SoftHyphen}"), "", null);
+ count += OpenXmlRegex.Replace(paras, new Regex("use"), "no longer use", null);
+ Console.WriteLine("Example #24 Replaced: {0}", count);
+
+ // The next example deals with symbols (i.e., w:sym elements).
+ // To work with symbols, you should acquire the Unicode values for the
+ // symbols you wish to match or use in replacement patterns. The reason
+ // is that UnicodeMapper will (a) mimic Microsoft Word in shifting the
+ // Unicode values into the Unicode private use area (by adding U+F000)
+ // and (b) use replacements for Unicode values that have been used in
+ // conjunction with different fonts already (by adding U+E000).
+ //
+ // The replacement Únicode values will depend on the order in which
+ // symbols are retrieved. Therefore, you should not rely on any fixed
+ // assignment.
+ //
+ // In the example below, pencil will be represented by U+F021, whereas
+ // spider (same value with different font) will be represented by U+E001.
+ // If spider had been assigned first, spider would be U+F021 and pencil
+ // would be U+E001.
+ char oldPhone = UnicodeMapper.SymToChar("Wingdings", 40);
+ char newPhone = UnicodeMapper.SymToChar("Wingdings", 41);
+ char pencil = UnicodeMapper.SymToChar("Wingdings", 0x21);
+ char spider = UnicodeMapper.SymToChar("Webdings", 0x21);
+
+ // Replace or comment on symbols (paragraph 23)
+ paras = xDoc.Descendants(W.p).Skip(22).Take(1).ToList();
+ count = OpenXmlRegex.Replace(paras, new Regex($"{oldPhone}"), $"{newPhone} (replaced with new phone)", null);
+ count += OpenXmlRegex.Replace(paras, new Regex($"({pencil})"), "$1 (same pencil)", null);
+ count += OpenXmlRegex.Replace(paras, new Regex($"({spider})"), "$1 (same spider)", null);
+ Console.WriteLine("Example #25 Replaced: {0}", count);
+
+ wDoc.MainDocumentPart.PutXDocument();
+ }
+
+ var sourcePres = new FileInfo("../../TestPresentation.pptx");
+ var newPres = new FileInfo(Path.Combine(tempDi.FullName, "Modified.pptx"));
+ File.Copy(sourcePres.FullName, newPres.FullName);
+ using (PresentationDocument pDoc = PresentationDocument.Open(newPres.FullName, true))
+ {
+ foreach (SlidePart slidePart in pDoc.PresentationPart.SlideParts)
+ {
+ XDocument xDoc = slidePart.GetXDocument();
+
+ // Replace content
+ IEnumerable<XElement> content = xDoc.Descendants(A.p);
+ var regex = new Regex("Hello");
+ int count = OpenXmlRegex.Replace(content, regex, "H e l l o", null);
+ Console.WriteLine("Example #18 Replaced: {0}", count);
+
+ // If you absolutely want to preserve compatibility with PowerPoint 2007, then you will need to strip the xml:space="preserve" attribute throughout.
+ // This is an issue for PowerPoint only, not Word, and for 2007 only.
+ // The side-effect of this is that if a run has space at the beginning or end of it, the space will be stripped upon loading, and content/layout will be affected.
+ xDoc.Descendants().Attributes(XNamespace.Xml + "space").Remove();
+
+ slidePart.PutXDocument();
+ }
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex01/OpenXmlRegex01.csproj b/OpenXmlPowerToolsExamples/OpenXmlRegex01/OpenXmlRegex01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex01/OpenXmlRegex01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex01/TestDocument.docx b/OpenXmlPowerToolsExamples/OpenXmlRegex01/TestDocument.docx
new file mode 100644
index 0000000..f1cc3b0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex01/TestDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex01/TestPresentation.pptx b/OpenXmlPowerToolsExamples/OpenXmlRegex01/TestPresentation.pptx
new file mode 100644
index 0000000..2f83408
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex01/TestPresentation.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex02/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/OpenXmlRegex02/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex02/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex02/OpenXmlRegex02.cs b/OpenXmlPowerToolsExamples/OpenXmlRegex02/OpenXmlRegex02.cs
new file mode 100644
index 0000000..ab2af38
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex02/OpenXmlRegex02.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class OpenXmlRegexExample
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ var sourceDoc = new FileInfo("../../TestDocument.docx");
+ var newDoc = new FileInfo("Modified.docx");
+ if (newDoc.Exists)
+ newDoc.Delete();
+ File.Copy(sourceDoc.FullName, newDoc.FullName);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(newDoc.FullName, true))
+ {
+ int count;
+ var xDoc = wDoc.MainDocumentPart.GetXDocument();
+ Regex regex;
+ IEnumerable<XElement> content;
+
+ content = xDoc.Descendants(W.p);
+ regex = new Regex("[.]\x020+");
+ count = OpenXmlRegex.Replace(content, regex, "." + Environment.NewLine, null);
+
+ foreach (var para in content)
+ {
+ var newPara = (XElement)TransformEnvironmentNewLineToParagraph(para);
+ para.ReplaceNodes(newPara.Nodes());
+ }
+
+ wDoc.MainDocumentPart.PutXDocument();
+ }
+ }
+
+ private static object TransformEnvironmentNewLineToParagraph(XNode node)
+ {
+ var element = node as XElement;
+ if (element != null)
+ {
+ if (element.Name == W.p)
+ {
+
+ }
+
+ return new XElement(element.Name,
+ element.Attributes(),
+ element.Nodes().Select(n => TransformEnvironmentNewLineToParagraph(n)));
+ }
+ return node;
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex02/OpenXmlRegex02.csproj b/OpenXmlPowerToolsExamples/OpenXmlRegex02/OpenXmlRegex02.csproj
new file mode 100644
index 0000000..d1fe6d3
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex02/OpenXmlRegex02.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.7.1" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/OpenXmlRegex02/TestDocument.docx b/OpenXmlPowerToolsExamples/OpenXmlRegex02/TestDocument.docx
new file mode 100644
index 0000000..a9c2e8d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/OpenXmlRegex02/TestDocument.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PivotTables01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/PivotTables01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PivotTables01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/PivotTables01/PivotData.txt b/OpenXmlPowerToolsExamples/PivotTables01/PivotData.txt
new file mode 100644
index 0000000..9d0ec62
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PivotTables01/PivotData.txt
@@ -0,0 +1,61 @@
+Year,Quarter,Region,Category,Product,Amount
+2010,Q1,North,Bicycles,B100,3448.00
+2010,Q1,North,Bicycles,B200,8448.00
+2010,Q1,North,Bicycles,B300,922.00
+2010,Q1,North,Accessories,Mirrors,303.00
+2010,Q1,North,Accessories,Carriers,455.00
+2010,Q1,South,Bicycles,B100,2887.00
+2010,Q1,South,Bicycles,B200,4477.00
+2010,Q1,South,Bicycles,B300,1011.00
+2010,Q1,South,Accessories,Mirrors,80.00
+2010,Q1,South,Accessories,Carriers,205.00
+2010,Q2,North,Bicycles,B100,4099.00
+2010,Q2,North,Bicycles,B200,8333.00
+2010,Q2,North,Bicycles,B300,355.00
+2010,Q2,North,Accessories,Mirrors,440.00
+2010,Q2,North,Accessories,Carriers,120.00
+2010,Q2,South,Bicycles,B100,6020.00
+2010,Q2,South,Bicycles,B200,10822.00
+2010,Q2,South,Bicycles,B300,0.00
+2010,Q2,South,Accessories,Mirrors,105.00
+2010,Q2,South,Accessories,Carriers,922.00
+2010,Q3,North,Bicycles,B100,4403.00
+2010,Q3,North,Bicycles,B200,7878.00
+2010,Q3,North,Bicycles,B300,106.00
+2010,Q3,North,Accessories,Mirrors,89.00
+2010,Q3,North,Accessories,Carriers,610.00
+2010,Q3,South,Bicycles,B100,6699.00
+2010,Q3,South,Bicycles,B200,7099.00
+2010,Q3,South,Bicycles,B300,670.00
+2010,Q3,South,Accessories,Mirrors,277.00
+2010,Q3,South,Accessories,Carriers,490.00
+2010,Q4,North,Bicycles,B100,2204.00
+2010,Q4,North,Bicycles,B200,5500.00
+2010,Q4,North,Bicycles,B300,0.00
+2010,Q4,North,Accessories,Mirrors,45.00
+2010,Q4,North,Accessories,Carriers,600.00
+2010,Q4,South,Bicycles,B100,2008.00
+2010,Q4,South,Bicycles,B200,10220.00
+2010,Q4,South,Bicycles,B300,244.00
+2010,Q4,South,Accessories,Mirrors,448.00
+2010,Q4,South,Accessories,Carriers,327.00
+2011,Q1,North,Bicycles,B100,9112.00
+2011,Q1,North,Bicycles,B200,1018.00
+2011,Q1,North,Bicycles,B300,90.00
+2011,Q1,North,Accessories,Mirrors,244.00
+2011,Q1,North,Accessories,Carriers,670.00
+2011,Q1,South,Bicycles,B100,8022.00
+2011,Q1,South,Bicycles,B200,8199.00
+2011,Q1,South,Bicycles,B300,888.00
+2011,Q1,South,Accessories,Mirrors,290.00
+2011,Q1,South,Accessories,Carriers,344.00
+2011,Q2,North,Bicycles,B100,3248.00
+2011,Q2,North,Bicycles,B200,8748.00
+2011,Q2,North,Bicycles,B300,972.00
+2011,Q2,North,Accessories,Mirrors,603.00
+2011,Q2,North,Accessories,Carriers,485.00
+2011,Q2,South,Bicycles,B100,3478.00
+2011,Q2,South,Bicycles,B200,8498.00
+2011,Q2,South,Bicycles,B300,282.00
+2011,Q2,South,Accessories,Mirrors,193.00
+2011,Q2,South,Accessories,Carriers,667.00
diff --git a/OpenXmlPowerToolsExamples/PivotTables01/PivotTables.cs b/OpenXmlPowerToolsExamples/PivotTables01/PivotTables.cs
new file mode 100644
index 0000000..d506c6f
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PivotTables01/PivotTables.cs
@@ -0,0 +1,456 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using System.Xml.Linq;
+using System.IO;
+using System.Timers;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace ExamplePivotTables
+{
+ class PivotTableExample
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ // Update an existing pivot table
+ FileInfo qs = new FileInfo("../../QuarterlySales.xlsx");
+ FileInfo qsu = new FileInfo(Path.Combine(tempDi.FullName, "QuarterlyPivot.xlsx"));
+
+ int row = 1;
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(
+ SmlDocument.FromFileName(qs.FullName)))
+ {
+ using (SpreadsheetDocument doc = streamDoc.GetSpreadsheetDocument())
+ {
+ WorksheetPart sheet = WorksheetAccessor.GetWorksheet(doc, "Range");
+ using (StreamReader source = new StreamReader("../../PivotData.txt"))
+ {
+ while (!source.EndOfStream)
+ {
+ string line = source.ReadLine();
+ if (line.Length > 3)
+ {
+ string[] fields = line.Split(',');
+ int column = 1;
+ foreach (string item in fields)
+ {
+ double num;
+ if (double.TryParse(item, out num))
+ WorksheetAccessor.SetCellValue(doc, sheet, row, column++, num);
+ else
+ WorksheetAccessor.SetCellValue(doc, sheet, row, column++, item);
+ }
+ }
+ row++;
+ }
+ }
+ sheet.PutXDocument();
+
+ WorksheetAccessor.UpdateRangeEndRow(doc, "Sales", row - 1);
+ }
+ streamDoc.GetModifiedSmlDocument().SaveAs(qsu.FullName);
+ }
+
+ // Create from scratch
+ row = 1;
+ int maxColumn = 1;
+ using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreateSpreadsheetDocument())
+ {
+ using (SpreadsheetDocument doc = streamDoc.GetSpreadsheetDocument())
+ {
+ WorksheetAccessor.CreateDefaultStyles(doc);
+ WorksheetPart sheet = WorksheetAccessor.AddWorksheet(doc, "Range");
+ MemorySpreadsheet ms = new MemorySpreadsheet();
+
+#if false
+ int font0 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 1),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font2 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 18,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 3),
+ Name = "Cambria",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Major
+ });
+ int font3 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 15,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 3),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font4 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 13,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 3),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font5 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 3),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font6 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FF006100"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font7 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FF9C0006"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font8 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FF9C6500"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font9 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FF3F3F76"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font10 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FF3F3F3F"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font11 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FFFA7D00"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font12 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FFFA7D00"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font13 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 0),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font14 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FFFF0000"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font15 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Italic = true,
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo("FF7F7F7F"),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font16 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Bold = true,
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 1),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+ int font17 = WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Size = 11,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 0),
+ Name = "Calibri",
+ Family = 2,
+ Scheme = WorksheetAccessor.Font.SchemeType.Minor
+ });
+
+ int fill0 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.None, null, null));
+ int fill1 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Gray125, null, null));
+ int fill2 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFC6EFCE")));
+ int fill3 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFFFC7CE")));
+ int fill4 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFFFEB9C")));
+ int fill5 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFFFCC99")));
+ int fill6 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFF2F2F2")));
+ int fill7 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFA5A5A5")));
+ int fill8 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo("FFFFFFCC")));
+ int fill9 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 4)));
+ int fill10 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(4, 0.79998168889431442)));
+ int fill11 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(4, 0.59999389629810485)));
+ int fill12 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(4, 0.39997558519241921)));
+ int fill13 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 5)));
+ int fill14 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(5, 0.79998168889431442)));
+ int fill15 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(5, 0.59999389629810485)));
+ int fill16 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(5, 0.39997558519241921)));
+ int fill17 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 6)));
+ int fill18 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(6, 0.79998168889431442)));
+ int fill19 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(6, 0.59999389629810485)));
+ int fill20 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(6, 0.39997558519241921)));
+ int fill21 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 7)));
+ int fill22 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(7, 0.79998168889431442)));
+ int fill23 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(7, 0.59999389629810485)));
+ int fill24 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(7, 0.39997558519241921)));
+ int fill25 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 8)));
+ int fill26 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(8, 0.79998168889431442)));
+ int fill27 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(8, 0.59999389629810485)));
+ int fill28 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(8, 0.39997558519241921)));
+ int fill29 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ null, new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 9)));
+ int fill30 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(9, 0.79998168889431442)));
+ int fill31 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(9, 0.59999389629810485)));
+ int fill32 = WorksheetAccessor.GetFillIndex(doc, new WorksheetAccessor.PatternFill(WorksheetAccessor.PatternFill.PatternType.Solid,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Indexed, 65),
+ new WorksheetAccessor.ColorInfo(9, 0.39997558519241921)));
+
+ int border1 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thick,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 4))
+ });
+ int border2 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thick, new WorksheetAccessor.ColorInfo(4, 0.499984740745262))
+ });
+ int border3 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Medium, new WorksheetAccessor.ColorInfo(4, 0.39997558519241921))
+ });
+ int border4 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Left = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF7F7F7F")),
+ Right = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF7F7F7F")),
+ Top = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF7F7F7F")),
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF7F7F7F"))
+ });
+ int border5 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Left = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF3F3F3F")),
+ Right = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF3F3F3F")),
+ Top = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF3F3F3F")),
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF3F3F3F"))
+ });
+ int border6 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Double, new WorksheetAccessor.ColorInfo("FFFF8001"))
+ });
+ int border7 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Left = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Double, new WorksheetAccessor.ColorInfo("FF3F3F3F")),
+ Right = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Double, new WorksheetAccessor.ColorInfo("FF3F3F3F")),
+ Top = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Double, new WorksheetAccessor.ColorInfo("FF3F3F3F")),
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Double, new WorksheetAccessor.ColorInfo("FF3F3F3F"))
+ });
+ int border8 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Left = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FFB2B2B2")),
+ Right = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FFB2B2B2")),
+ Top = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FFB2B2B2")),
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FFB2B2B2"))
+ });
+ int border9 = WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ Top = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 4)),
+ Bottom = new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Double,
+ new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 4))
+ });
+#endif
+
+ int southIndex = WorksheetAccessor.GetStyleIndex(doc, 0, 8, 1, 2,
+ new WorksheetAccessor.CellAlignment { HorizontalAlignment = WorksheetAccessor.CellAlignment.Horizontal.Center },
+ true, false);
+ WorksheetAccessor.GradientFill gradient = new WorksheetAccessor.GradientFill(90);
+ gradient.AddStop(new WorksheetAccessor.GradientStop(0, new WorksheetAccessor.ColorInfo("FF92D050")));
+ gradient.AddStop(new WorksheetAccessor.GradientStop(1, new WorksheetAccessor.ColorInfo("FF0070C0")));
+ int northIndex = WorksheetAccessor.GetStyleIndex(doc, 0,
+ WorksheetAccessor.GetFontIndex(doc, new WorksheetAccessor.Font
+ {
+ Italic = true,
+ Size = 8,
+ Color = new WorksheetAccessor.ColorInfo(WorksheetAccessor.ColorInfo.ColorType.Theme, 1),
+ Name = "Times New Roman",
+ Family = 1
+ }),
+ WorksheetAccessor.GetFillIndex(doc, gradient),
+ WorksheetAccessor.GetBorderIndex(doc, new WorksheetAccessor.Border
+ {
+ DiagonalDown = true,
+ Diagonal =
+ new WorksheetAccessor.BorderLine(WorksheetAccessor.BorderLine.LineStyle.Thin, new WorksheetAccessor.ColorInfo("FF616100"))
+ }),
+ null, false, false);
+ WorksheetAccessor.CheckNumberFormat(doc, 100, "_(\"$\"* #,##0.00_);_(\"$\"* \\(#,##0.00\\);_(\"$\"* \"-\"??_);_(@_)");
+ int amountIndex = WorksheetAccessor.GetStyleIndex(doc, 100, 0, 0, 0, null, false, false);
+
+ using (StreamReader source = new StreamReader("../../PivotData.txt"))
+ {
+ while (!source.EndOfStream)
+ {
+ string line = source.ReadLine();
+ if (line.Length > 3)
+ {
+ string[] fields = line.Split(',');
+ int column = 1;
+ foreach (string item in fields)
+ {
+ double num;
+ if (double.TryParse(item, out num))
+ {
+ if (column == 6)
+ ms.SetCellValue(row, column++, num, amountIndex);
+ else
+ ms.SetCellValue(row, column++, num);
+ }
+ else if (item == "Accessories")
+ ms.SetCellValue(row, column++, item, WorksheetAccessor.GetStyleIndex(doc, "Good"));
+ else if (item == "South")
+ ms.SetCellValue(row, column++, item, southIndex);
+ else if (item == "North")
+ ms.SetCellValue(row, column++, item, northIndex);
+ else
+ ms.SetCellValue(row, column++, item);
+ }
+ maxColumn = column - 1;
+ }
+ row++;
+ }
+ }
+ WorksheetAccessor.SetSheetContents(doc, sheet, ms);
+ WorksheetAccessor.SetRange(doc, "Sales", "Range", 1, 1, row - 1, maxColumn);
+ WorksheetPart pivot = WorksheetAccessor.AddWorksheet(doc, "Pivot");
+ WorksheetAccessor.CreatePivotTable(doc, "Sales", pivot);
+
+ // Configure pivot table rows, columns, data and filters
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Year", WorksheetAccessor.PivotAxis.Column);
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Quarter", WorksheetAccessor.PivotAxis.Column);
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Category", WorksheetAccessor.PivotAxis.Row);
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Product", WorksheetAccessor.PivotAxis.Row);
+ WorksheetAccessor.AddDataValue(doc, pivot, "Amount");
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Region", WorksheetAccessor.PivotAxis.Page);
+ }
+ streamDoc.GetModifiedSmlDocument().SaveAs(Path.Combine(tempDi.FullName, "NewPivot.xlsx"));
+ }
+
+
+ // Add pivot table to existing spreadsheet
+ // Demonstrate multiple data fields
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(
+ SmlDocument.FromFileName("../../QuarterlyUnitSales.xlsx")))
+ {
+ using (SpreadsheetDocument doc = streamDoc.GetSpreadsheetDocument())
+ {
+ WorksheetPart pivot = WorksheetAccessor.AddWorksheet(doc, "Pivot");
+ WorksheetAccessor.CreatePivotTable(doc, "Sales", pivot);
+
+ // Configure pivot table rows, columns, data and filters
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Year", WorksheetAccessor.PivotAxis.Column);
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Quarter", WorksheetAccessor.PivotAxis.Column);
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Category", WorksheetAccessor.PivotAxis.Row);
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Product", WorksheetAccessor.PivotAxis.Row);
+ WorksheetAccessor.AddDataValue(doc, pivot, "Total");
+ WorksheetAccessor.AddDataValue(doc, pivot, "Quantity");
+ WorksheetAccessor.AddDataValue(doc, pivot, "Unit Price");
+ WorksheetAccessor.AddPivotAxis(doc, pivot, "Region", WorksheetAccessor.PivotAxis.Page);
+ }
+ streamDoc.GetModifiedSmlDocument().SaveAs(Path.Combine(tempDi.FullName, "QuarterlyUnitSalesWithPivot.xlsx"));
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/PivotTables01/PivotTables01.csproj b/OpenXmlPowerToolsExamples/PivotTables01/PivotTables01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PivotTables01/PivotTables01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/PivotTables01/QuarterlySales.xlsx b/OpenXmlPowerToolsExamples/PivotTables01/QuarterlySales.xlsx
new file mode 100644
index 0000000..3394556
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PivotTables01/QuarterlySales.xlsx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PivotTables01/QuarterlyUnitSales.xlsx b/OpenXmlPowerToolsExamples/PivotTables01/QuarterlyUnitSales.xlsx
new file mode 100644
index 0000000..e1aea65
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PivotTables01/QuarterlyUnitSales.xlsx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Companies.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Companies.pptx
new file mode 100644
index 0000000..641af8d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Companies.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso One.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso One.pptx
new file mode 100644
index 0000000..4f89877
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso One.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso Three.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso Three.pptx
new file mode 100644
index 0000000..5f8b962
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso Three.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso Two.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso Two.pptx
new file mode 100644
index 0000000..06432bf
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso Two.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso.pptx
new file mode 100644
index 0000000..752cf6e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Contoso.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Customer Content.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Customer Content.pptx
new file mode 100644
index 0000000..02baedb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Customer Content.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/PresentationBuilder01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation One.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation One.pptx
new file mode 100644
index 0000000..0497562
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation One.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation Three.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation Three.pptx
new file mode 100644
index 0000000..4a70f95
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation Three.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation Two.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation Two.pptx
new file mode 100644
index 0000000..d0f7e82
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/Presentation Two.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/PresentationBuilder01.cs b/OpenXmlPowerToolsExamples/PresentationBuilder01/PresentationBuilder01.cs
new file mode 100644
index 0000000..db9a213
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/PresentationBuilder01.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using OpenXmlPowerTools;
+
+namespace ExamplePresentatonBuilder01
+{
+ class ExamplePresentationBuilder01
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ string source1 = "../../Contoso.pptx";
+ string source2 = "../../Companies.pptx";
+ string source3 = "../../Customer Content.pptx";
+ string source4 = "../../Presentation One.pptx";
+ string source5 = "../../Presentation Two.pptx";
+ string source6 = "../../Presentation Three.pptx";
+ string contoso1 = "../../Contoso One.pptx";
+ string contoso2 = "../../Contoso Two.pptx";
+ string contoso3 = "../../Contoso Three.pptx";
+ List<SlideSource> sources = null;
+
+ var sourceDoc = new PmlDocument(source1);
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(sourceDoc, 0, 1, false), // Title
+ new SlideSource(sourceDoc, 1, 1, false), // First intro (of 3)
+ new SlideSource(sourceDoc, 4, 2, false), // Sales bios
+ new SlideSource(sourceDoc, 9, 3, false), // Content slides
+ new SlideSource(sourceDoc, 13, 1, false), // Closing summary
+ };
+ PresentationBuilder.BuildPresentation(sources, Path.Combine(tempDi.FullName, "Out1.pptx"));
+
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source2), 2, 1, true), // Choose company
+ new SlideSource(new PmlDocument(source3), false), // Content
+ };
+ PresentationBuilder.BuildPresentation(sources, Path.Combine(tempDi.FullName, "Out2.pptx"));
+
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(source4), true),
+ new SlideSource(new PmlDocument(source5), true),
+ new SlideSource(new PmlDocument(source6), true),
+ };
+ PresentationBuilder.BuildPresentation(sources, Path.Combine(tempDi.FullName, "Out3.pptx"));
+
+ sources = new List<SlideSource>()
+ {
+ new SlideSource(new PmlDocument(contoso1), true),
+ new SlideSource(new PmlDocument(contoso2), true),
+ new SlideSource(new PmlDocument(contoso3), true),
+ };
+ PresentationBuilder.BuildPresentation(sources, Path.Combine(tempDi.FullName, "Out4.pptx"));
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder01/PresentationBuilder01.csproj b/OpenXmlPowerToolsExamples/PresentationBuilder01/PresentationBuilder01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder01/PresentationBuilder01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder02/HiddenPresentation.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder02/HiddenPresentation.pptx
new file mode 100644
index 0000000..b37a76b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder02/HiddenPresentation.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder02/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/PresentationBuilder02/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder02/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder02/Presentation1.pptx b/OpenXmlPowerToolsExamples/PresentationBuilder02/Presentation1.pptx
new file mode 100644
index 0000000..12e7d78
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder02/Presentation1.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder02/PresentationBuilder02.cs b/OpenXmlPowerToolsExamples/PresentationBuilder02/PresentationBuilder02.cs
new file mode 100644
index 0000000..6d11246
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder02/PresentationBuilder02.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace PresentationBuilder02
+{
+ class PresentationBuilder02
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ string presentation = "../../Presentation1.pptx";
+ string hiddenPresentation = "../../HiddenPresentation.pptx";
+
+ // First, load both presentations into byte arrays, simulating retrieving presentations from some source
+ // such as a SharePoint server
+ var baPresentation = File.ReadAllBytes(presentation);
+ var baHiddenPresentation = File.ReadAllBytes(hiddenPresentation);
+
+ // Next, replace "thee" with "the" in the main presentation
+ var pmlMainPresentation = new PmlDocument("Main.pptx", baPresentation);
+ PmlDocument modifiedMainPresentation = null;
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(pmlMainPresentation))
+ {
+ using (PresentationDocument document = streamDoc.GetPresentationDocument())
+ {
+ var pXDoc = document.PresentationPart.GetXDocument();
+ foreach (var slideId in pXDoc.Root.Elements(P.sldIdLst).Elements(P.sldId))
+ {
+ var slideRelId = (string)slideId.Attribute(R.id);
+ var slidePart = document.PresentationPart.GetPartById(slideRelId);
+ var slideXDoc = slidePart.GetXDocument();
+ var paragraphs = slideXDoc.Descendants(A.p).ToList();
+ OpenXmlRegex.Replace(paragraphs, new Regex("thee"), "the", null);
+ slidePart.PutXDocument();
+ }
+ }
+ modifiedMainPresentation = streamDoc.GetModifiedPmlDocument();
+ }
+
+ // Combine the two presentations into a single presentation
+ var slideSources = new List<SlideSource>() {
+ new SlideSource(modifiedMainPresentation, 0, 1, true),
+ new SlideSource(new PmlDocument("Hidden.pptx", baHiddenPresentation), true),
+ new SlideSource(modifiedMainPresentation, 1, true),
+ };
+ PmlDocument combinedPresentation = PresentationBuilder.BuildPresentation(slideSources);
+
+ // Replace <# TRADEMARK #> with AdventureWorks (c)
+ PmlDocument modifiedCombinedPresentation = null;
+ using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(combinedPresentation))
+ {
+ using (PresentationDocument document = streamDoc.GetPresentationDocument())
+ {
+ var pXDoc = document.PresentationPart.GetXDocument();
+ foreach (var slideId in pXDoc.Root.Elements(P.sldIdLst).Elements(P.sldId).Skip(1).Take(1))
+ {
+ var slideRelId = (string)slideId.Attribute(R.id);
+ var slidePart = document.PresentationPart.GetPartById(slideRelId);
+ var slideXDoc = slidePart.GetXDocument();
+ var paragraphs = slideXDoc.Descendants(A.p).ToList();
+ OpenXmlRegex.Replace(paragraphs, new Regex("<# TRADEMARK #>"), "AdventureWorks (c)", null);
+ slidePart.PutXDocument();
+ }
+ }
+ modifiedCombinedPresentation = streamDoc.GetModifiedPmlDocument();
+ }
+
+ // we now have a PmlDocument (which is essentially a byte array) that can be saved as necessary.
+ modifiedCombinedPresentation.SaveAs(Path.Combine(tempDi.FullName, "Modified.pptx"));
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/PresentationBuilder02/PresentationBuilder02.csproj b/OpenXmlPowerToolsExamples/PresentationBuilder02/PresentationBuilder02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/PresentationBuilder02/PresentationBuilder02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/ReferenceAdder01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/ReferenceAdder01.cs b/OpenXmlPowerToolsExamples/ReferenceAdder01/ReferenceAdder01.cs
new file mode 100644
index 0000000..9037bf1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/ReferenceAdder01.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class TestTocAdder
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ DirectoryInfo di2 = new DirectoryInfo("../../");
+ foreach (var file in di2.GetFiles("*.docx"))
+ file.CopyTo(Path.Combine(tempDi.FullName, file.Name));
+
+ List<string> filesToProcess = new List<string>();
+
+ // Inserts a basic TOC before the first paragraph of the document
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test01.docx"), true))
+ {
+ ReferenceAdder.AddToc(wdoc, "/w:document/w:body/w:p[1]",
+ @"TOC \o '1-3' \h \z \u", null, null);
+ }
+
+ // Inserts a TOC after the title of the document
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test02.docx"), true))
+ {
+ ReferenceAdder.AddToc(wdoc, "/w:document/w:body/w:p[2]",
+ @"TOC \o '1-3' \h \z \u", null, null);
+ }
+
+ // Inserts a TOC with a different title
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test03.docx"), true))
+ {
+ ReferenceAdder.AddToc(wdoc, "/w:document/w:body/w:p[1]",
+ @"TOC \o '1-3' \h \z \u", "Table of Contents", null);
+ }
+
+ // Inserts a TOC that includes headings through level 4
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test04.docx"), true))
+ {
+ ReferenceAdder.AddToc(wdoc, "/w:document/w:body/w:p[1]",
+ @"TOC \o '1-4' \h \z \u", null, null);
+ }
+
+ // Inserts a table of figures
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test05.docx"), true))
+ {
+ ReferenceAdder.AddTof(wdoc, "/w:document/w:body/w:p[2]",
+ @"TOC \h \z \c ""Figure""", null);
+ }
+
+ // Inserts a basic TOC before the first paragraph of the document.
+ // Test06.docx does not include a StylesWithEffects part.
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test06.docx"), true))
+ {
+ ReferenceAdder.AddToc(wdoc, "/w:document/w:body/w:p[1]",
+ @"TOC \o '1-3' \h \z \u", null, null);
+ }
+
+ // Inserts a TOA before the first paragraph of the document.
+ using (WordprocessingDocument wdoc =
+ WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test07.docx"), true))
+ {
+ ReferenceAdder.AddToa(wdoc, "/w:document/w:body/w:p[2]",
+ @"TOA \h \c ""1"" \p", null);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/ReferenceAdder01.csproj b/OpenXmlPowerToolsExamples/ReferenceAdder01/ReferenceAdder01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/ReferenceAdder01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test01.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test01.docx
new file mode 100644
index 0000000..1342b7d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test02.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test02.docx
new file mode 100644
index 0000000..825119e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test03.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test03.docx
new file mode 100644
index 0000000..1342b7d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test04.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test04.docx
new file mode 100644
index 0000000..cabc8ba
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test05.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test05.docx
new file mode 100644
index 0000000..c10db01
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test06.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test06.docx
new file mode 100644
index 0000000..b66be7c
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/ReferenceAdder01/Test07.docx b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test07.docx
new file mode 100644
index 0000000..b80d16d
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/ReferenceAdder01/Test07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/RevisionAccepter01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/RevisionAccepter01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/RevisionAccepter01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/RevisionAccepter01/RevisionAccepter01.cs b/OpenXmlPowerToolsExamples/RevisionAccepter01/RevisionAccepter01.cs
new file mode 100644
index 0000000..7372b23
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/RevisionAccepter01/RevisionAccepter01.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using OpenXmlPowerTools;
+
+namespace RevisionAccepterExample
+{
+ class RevisionAccepterExample
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ // Accept all revisions, save result as a new document
+ WmlDocument result = RevisionAccepter.AcceptRevisions(new WmlDocument("../../Source1.docx"));
+ result.SaveAs(Path.Combine(tempDi.FullName, "Out1.docx"));
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/RevisionAccepter01/RevisionAccepter01.csproj b/OpenXmlPowerToolsExamples/RevisionAccepter01/RevisionAccepter01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/RevisionAccepter01/RevisionAccepter01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/RevisionAccepter01/Source1.docx b/OpenXmlPowerToolsExamples/RevisionAccepter01/Source1.docx
new file mode 100644
index 0000000..10b4281
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/RevisionAccepter01/Source1.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/SmlDataRetriever01/SampleSpreadsheet.xlsx b/OpenXmlPowerToolsExamples/SmlDataRetriever01/SampleSpreadsheet.xlsx
new file mode 100644
index 0000000..32afde1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SmlDataRetriever01/SampleSpreadsheet.xlsx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/SmlDataRetriever01/SmlDataRetriever01.cs b/OpenXmlPowerToolsExamples/SmlDataRetriever01/SmlDataRetriever01.cs
new file mode 100644
index 0000000..3d94c9b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SmlDataRetriever01/SmlDataRetriever01.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class SmlDataRetriever01
+ {
+ static void Main(string[] args)
+ {
+ FileInfo fi = null;
+ fi = new FileInfo("../../SampleSpreadsheet.xlsx");
+
+ // Retrieve range from Sheet1
+ XElement data = SmlDataRetriever.RetrieveRange(fi.FullName, "Sheet1", "A1:C3");
+ Console.WriteLine(data);
+
+ // Retrieve entire sheet
+ data = SmlDataRetriever.RetrieveSheet(fi.FullName, "Sheet1");
+ Console.WriteLine(data);
+
+ // Retrieve table
+ data = SmlDataRetriever.RetrieveTable(fi.FullName, "VehicleTable");
+ Console.WriteLine(data);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/SmlDataRetriever01/SmlDataRetriever01.csproj b/OpenXmlPowerToolsExamples/SmlDataRetriever01/SmlDataRetriever01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SmlDataRetriever01/SmlDataRetriever01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/SpreadsheetWriter01/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/SpreadsheetWriter01/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SpreadsheetWriter01/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/SpreadsheetWriter01/SpreadsheetWriter01.cs b/OpenXmlPowerToolsExamples/SpreadsheetWriter01/SpreadsheetWriter01.cs
new file mode 100644
index 0000000..79bc2c8
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SpreadsheetWriter01/SpreadsheetWriter01.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace SpreadsheetWriterExample
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ WorkbookDfn wb = new WorkbookDfn
+ {
+ Worksheets = new WorksheetDfn[]
+ {
+ new WorksheetDfn
+ {
+ Name = "MyFirstSheet",
+ TableName = "NamesAndRates",
+ ColumnHeadings = new CellDfn[]
+ {
+ new CellDfn
+ {
+ Value = "Name",
+ Bold = true,
+ },
+ new CellDfn
+ {
+ Value = "Age",
+ Bold = true,
+ HorizontalCellAlignment = HorizontalCellAlignment.Left,
+ },
+ new CellDfn
+ {
+ Value = "Rate",
+ Bold = true,
+ HorizontalCellAlignment = HorizontalCellAlignment.Left,
+ }
+ },
+ Rows = new RowDfn[]
+ {
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "Eric",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = 50,
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (decimal)45.00,
+ FormatCode = "0.00",
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "Bob",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = 42,
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (decimal)78.00,
+ FormatCode = "0.00",
+ },
+ }
+ },
+ }
+ }
+ }
+ };
+ SpreadsheetWriter.Write(Path.Combine(tempDi.FullName, "Test1.xlsx"), wb);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/SpreadsheetWriter01/SpreadsheetWriter01.csproj b/OpenXmlPowerToolsExamples/SpreadsheetWriter01/SpreadsheetWriter01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SpreadsheetWriter01/SpreadsheetWriter01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/SpreadsheetWriter02/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/SpreadsheetWriter02/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SpreadsheetWriter02/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/SpreadsheetWriter02/SpreadsheetWriter02.cs b/OpenXmlPowerToolsExamples/SpreadsheetWriter02/SpreadsheetWriter02.cs
new file mode 100644
index 0000000..522055b
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SpreadsheetWriter02/SpreadsheetWriter02.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace SpreadsheetWriterExample
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ WorkbookDfn wb = new WorkbookDfn
+ {
+ Worksheets = new WorksheetDfn[]
+ {
+ new WorksheetDfn
+ {
+ Name = "MyFirstSheet",
+ ColumnHeadings = new CellDfn[]
+ {
+ new CellDfn
+ {
+ Value = "DataType",
+ Bold = true,
+ },
+ new CellDfn
+ {
+ Value = "Value",
+ Bold = true,
+ HorizontalCellAlignment = HorizontalCellAlignment.Right,
+ },
+ },
+ Rows = new RowDfn[]
+ {
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "Boolean",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Boolean,
+ Value = true,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "Boolean",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Boolean,
+ Value = false,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "String",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "A String",
+ HorizontalCellAlignment = HorizontalCellAlignment.Right,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "int",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (int)100,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "int?",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (int?)100,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "int? (is null)",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = null,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "uint",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (uint)101,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "long",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = Int64.MaxValue,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "float",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (float)123.45,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "double",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (double)123.45,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.String,
+ Value = "decimal",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Number,
+ Value = (decimal)123.45,
+ },
+ }
+ },
+ new RowDfn
+ {
+ Cells = new CellDfn[]
+ {
+ new CellDfn {
+ CellDataType = CellDataType.Date,
+ Value = new DateTime(2012, 1, 8),
+ FormatCode = "mm-dd-yy",
+ },
+ new CellDfn {
+ CellDataType = CellDataType.Date,
+ Value = new DateTime(2012, 1, 9),
+ FormatCode = "mm-dd-yy",
+ Bold = true,
+ HorizontalCellAlignment = HorizontalCellAlignment.Center,
+ },
+ }
+ },
+ }
+ }
+ }
+ };
+ SpreadsheetWriter.Write(Path.Combine(tempDi.FullName, "Test2.xlsx"), wb);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/SpreadsheetWriter02/SpreadsheetWriter02.csproj b/OpenXmlPowerToolsExamples/SpreadsheetWriter02/SpreadsheetWriter02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/SpreadsheetWriter02/SpreadsheetWriter02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/TextReplacer01/Test01.pptx b/OpenXmlPowerToolsExamples/TextReplacer01/Test01.pptx
new file mode 100644
index 0000000..f543e6f
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer01/Test01.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer01/Test02.pptx b/OpenXmlPowerToolsExamples/TextReplacer01/Test02.pptx
new file mode 100644
index 0000000..ed21e54
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer01/Test02.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer01/Test03.pptx b/OpenXmlPowerToolsExamples/TextReplacer01/Test03.pptx
new file mode 100644
index 0000000..ed21e54
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer01/Test03.pptx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer01/TextReplacer01.cs b/OpenXmlPowerToolsExamples/TextReplacer01/TextReplacer01.cs
new file mode 100644
index 0000000..1b33777
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer01/TextReplacer01.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class TestPmlTextReplacer
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ File.Copy("../../Test01.pptx", Path.Combine(tempDi.FullName, "Test01out.pptx"));
+ using (PresentationDocument pDoc =
+ PresentationDocument.Open(Path.Combine(tempDi.FullName, "Test01out.pptx"), true))
+ {
+ TextReplacer.SearchAndReplace(pDoc, "Hello", "Goodbye", true);
+ }
+ File.Copy("../../Test02.pptx", Path.Combine(tempDi.FullName, "Test02out.pptx"));
+ using (PresentationDocument pDoc =
+ PresentationDocument.Open(Path.Combine(tempDi.FullName, "Test02out.pptx"), true))
+ {
+ TextReplacer.SearchAndReplace(pDoc, "Hello", "Goodbye", true);
+ }
+ File.Copy("../../Test03.pptx", Path.Combine(tempDi.FullName, "Test03out.pptx"));
+ using (PresentationDocument pDoc =
+ PresentationDocument.Open(Path.Combine(tempDi.FullName, "Test03out.pptx"), true))
+ {
+ TextReplacer.SearchAndReplace(pDoc, "Hello", "Goodbye", false);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/TextReplacer01/TextReplacer01.csproj b/OpenXmlPowerToolsExamples/TextReplacer01/TextReplacer01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer01/TextReplacer01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Note-ExampleOutput-files-are-in-bin-debug.txt b/OpenXmlPowerToolsExamples/TextReplacer02/Note-ExampleOutput-files-are-in-bin-debug.txt
new file mode 100644
index 0000000..b8881c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Note-ExampleOutput-files-are-in-bin-debug.txt
@@ -0,0 +1,3 @@
+Example output files are in a DateTime stamped directory in ./bin/debug. The directory name is ExampleOutput-yy-mm-dd-hhmmss.
+
+If you are building in release mode, they will, of course, be in ./bin/release.
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test01.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test01.docx
new file mode 100644
index 0000000..ac8eccd
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test02.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test02.docx
new file mode 100644
index 0000000..e463ef3
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test03.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test03.docx
new file mode 100644
index 0000000..f930fc5
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test04.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test04.docx
new file mode 100644
index 0000000..ac8eccd
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test05.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test05.docx
new file mode 100644
index 0000000..5c6c131
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test06.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test06.docx
new file mode 100644
index 0000000..7c9b68e
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test07.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test07.docx
new file mode 100644
index 0000000..a3b6fd1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test08.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test08.docx
new file mode 100644
index 0000000..fa2d5d7
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test08.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/Test09.docx b/OpenXmlPowerToolsExamples/TextReplacer02/Test09.docx
new file mode 100644
index 0000000..d3217c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/Test09.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/TextReplacer02.cs b/OpenXmlPowerToolsExamples/TextReplacer02/TextReplacer02.cs
new file mode 100644
index 0000000..faa30a4
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/TextReplacer02.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ DirectoryInfo di2 = new DirectoryInfo("../../");
+ foreach (var file in di2.GetFiles("*.docx"))
+ file.CopyTo(Path.Combine(tempDi.FullName, file.Name));
+
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test01.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", false);
+ try
+ {
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test02.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", false);
+ }
+ catch (Exception) { }
+ try
+ {
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test03.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", false);
+ }
+ catch (Exception) { }
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test04.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", true);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test05.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "is on", "is above", true);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test06.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", false);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test07.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", true);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test08.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "the", "this", true);
+ using (WordprocessingDocument doc = WordprocessingDocument.Open(Path.Combine(tempDi.FullName, "Test09.docx"), true))
+ TextReplacer.SearchAndReplace(doc, "===== Replace this text =====", "***zzz***", true);
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/TextReplacer02/TextReplacer02.csproj b/OpenXmlPowerToolsExamples/TextReplacer02/TextReplacer02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/TextReplacer02/TextReplacer02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/WmlComparer01/Source1.docx b/OpenXmlPowerToolsExamples/WmlComparer01/Source1.docx
new file mode 100644
index 0000000..66ec2f8
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer01/Source1.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlComparer01/Source2.docx b/OpenXmlPowerToolsExamples/WmlComparer01/Source2.docx
new file mode 100644
index 0000000..9334a8f
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer01/Source2.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlComparer01/WmlComparer01.cs b/OpenXmlPowerToolsExamples/WmlComparer01/WmlComparer01.cs
new file mode 100644
index 0000000..b7ce2a0
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer01/WmlComparer01.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class WmlComparer01
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ WmlComparerSettings settings = new WmlComparerSettings();
+ WmlDocument result = WmlComparer.Compare(
+ new WmlDocument("../../Source1.docx"),
+ new WmlDocument("../../Source2.docx"),
+ settings);
+ result.SaveAs(Path.Combine(tempDi.FullName, "Compared.docx"));
+
+ var revisions = WmlComparer.GetRevisions(result, settings);
+ foreach (var rev in revisions)
+ {
+ Console.WriteLine("Author: " + rev.Author);
+ Console.WriteLine("Revision type: " + rev.RevisionType);
+ Console.WriteLine("Revision text: " + rev.Text);
+ Console.WriteLine();
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/WmlComparer01/WmlComparer01.csproj b/OpenXmlPowerToolsExamples/WmlComparer01/WmlComparer01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer01/WmlComparer01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/WmlComparer02/Original.docx b/OpenXmlPowerToolsExamples/WmlComparer02/Original.docx
new file mode 100644
index 0000000..29bfaff
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer02/Original.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlComparer02/RevisedByBob.docx b/OpenXmlPowerToolsExamples/WmlComparer02/RevisedByBob.docx
new file mode 100644
index 0000000..3796163
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer02/RevisedByBob.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlComparer02/RevisedByMary.docx b/OpenXmlPowerToolsExamples/WmlComparer02/RevisedByMary.docx
new file mode 100644
index 0000000..69a5a59
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer02/RevisedByMary.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlComparer02/WmlComparer02.cs b/OpenXmlPowerToolsExamples/WmlComparer02/WmlComparer02.cs
new file mode 100644
index 0000000..2f843ee
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer02/WmlComparer02.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+namespace OpenXmlPowerTools
+{
+ class WmlComparer02
+ {
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ WmlDocument originalWml = new WmlDocument("../../Original.docx");
+ List<WmlRevisedDocumentInfo> revisedDocumentInfoList = new List<WmlRevisedDocumentInfo>()
+ {
+ new WmlRevisedDocumentInfo()
+ {
+ RevisedDocument = new WmlDocument("../../RevisedByBob.docx"),
+ Revisor = "Bob",
+ Color = Color.LightBlue,
+ },
+ new WmlRevisedDocumentInfo()
+ {
+ RevisedDocument = new WmlDocument("../../RevisedByMary.docx"),
+ Revisor = "Mary",
+ Color = Color.LightYellow,
+ },
+ };
+ WmlComparerSettings settings = new WmlComparerSettings();
+ WmlDocument consolidatedWml = WmlComparer.Consolidate(
+ originalWml,
+ revisedDocumentInfoList,
+ settings);
+ consolidatedWml.SaveAs(Path.Combine(tempDi.FullName, "Consolidated.docx"));
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/WmlComparer02/WmlComparer02.csproj b/OpenXmlPowerToolsExamples/WmlComparer02/WmlComparer02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlComparer02/WmlComparer02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/5DayTourPlanTemplate.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/5DayTourPlanTemplate.docx
new file mode 100644
index 0000000..394766a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/5DayTourPlanTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Contract.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Contract.docx
new file mode 100644
index 0000000..99a8965
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Contract.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Hebrew-01.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Hebrew-01.docx
new file mode 100644
index 0000000..9089200
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Hebrew-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Hebrew-02.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Hebrew-02.docx
new file mode 100644
index 0000000..2b80d67
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Hebrew-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/ResumeTemplate.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/ResumeTemplate.docx
new file mode 100644
index 0000000..c1e50fe
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/ResumeTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/TaskPlanTemplate.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/TaskPlanTemplate.docx
new file mode 100644
index 0000000..f5932f6
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/TaskPlanTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-01.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-01.docx
new file mode 100644
index 0000000..46ed41a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-02.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-02.docx
new file mode 100644
index 0000000..f6628ca
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-03.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-03.docx
new file mode 100644
index 0000000..676c8cb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-04.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-04.docx
new file mode 100644
index 0000000..797d694
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-05.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-05.docx
new file mode 100644
index 0000000..7df6284
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-06.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-06.docx
new file mode 100644
index 0000000..2d56602
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-07.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-07.docx
new file mode 100644
index 0000000..8971061
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-08.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-08.docx
new file mode 100644
index 0000000..0e21abe
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/Test-08.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/WmlToHtmlConverter01.cs b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/WmlToHtmlConverter01.cs
new file mode 100644
index 0000000..6d39c52
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/WmlToHtmlConverter01.cs
@@ -0,0 +1,154 @@
+/***************************************************************************
+
+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.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+
+class WmlToHtmlConverterHelper
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ /*
+ * This example loads each document into a byte array, then into a memory stream, so that the document can be opened for writing without
+ * modifying the source document.
+ */
+ foreach (var file in Directory.GetFiles("../../", "*.docx"))
+ {
+ ConvertToHtml(file, tempDi.FullName);
+ }
+ }
+
+ public static void ConvertToHtml(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ Console.WriteLine(fi.Name);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+
+ var pageTitle = fi.FullName;
+ var part = wDoc.CoreFilePropertiesPart;
+ if (part != null)
+ {
+ pageTitle = (string)part.GetXDocument().Descendants(DC.title).FirstOrDefault() ?? fi.FullName;
+ }
+
+ // TODO: Determine max-width from size of content area.
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
+ {
+ AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ DirectoryInfo localDirInfo = new DirectoryInfo(imageDirectoryName);
+ if (!localDirInfo.Exists)
+ localDirInfo.Create();
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ imageFormat = ImageFormat.Png;
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string imageFileName = imageDirectoryName + "/image" +
+ imageCounter.ToString() + "." + extension;
+ try
+ {
+ imageInfo.Bitmap.Save(imageFileName, imageFormat);
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+ string imageSource = localDirInfo.Name + "/image" +
+ imageCounter.ToString() + "." + extension;
+
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageSource),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement htmlElement = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Produce HTML document with <!DOCTYPE html > declaration to tell the browser
+ // we are using HTML5.
+ var html = new XDocument(
+ new XDocumentType("html", null, null, null),
+ htmlElement);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/WmlToHtmlConverter01.csproj b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/WmlToHtmlConverter01.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter01/WmlToHtmlConverter01.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/5DayTourPlanTemplate.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/5DayTourPlanTemplate.docx
new file mode 100644
index 0000000..394766a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/5DayTourPlanTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Contract.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Contract.docx
new file mode 100644
index 0000000..99a8965
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Contract.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Hebrew-01.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Hebrew-01.docx
new file mode 100644
index 0000000..9089200
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Hebrew-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Hebrew-02.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Hebrew-02.docx
new file mode 100644
index 0000000..2b80d67
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Hebrew-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/ResumeTemplate.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/ResumeTemplate.docx
new file mode 100644
index 0000000..c1e50fe
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/ResumeTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/TaskPlanTemplate.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/TaskPlanTemplate.docx
new file mode 100644
index 0000000..f5932f6
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/TaskPlanTemplate.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-01.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-01.docx
new file mode 100644
index 0000000..46ed41a
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-01.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-02.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-02.docx
new file mode 100644
index 0000000..f6628ca
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-02.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-03.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-03.docx
new file mode 100644
index 0000000..676c8cb
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-03.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-04.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-04.docx
new file mode 100644
index 0000000..797d694
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-04.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-05.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-05.docx
new file mode 100644
index 0000000..7df6284
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-05.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-06.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-06.docx
new file mode 100644
index 0000000..2d56602
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-06.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-07.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-07.docx
new file mode 100644
index 0000000..8971061
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-07.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-08.docx b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-08.docx
new file mode 100644
index 0000000..0e21abe
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/Test-08.docx
Binary files differ
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/WmlToHtmlConverter02.cs b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/WmlToHtmlConverter02.cs
new file mode 100644
index 0000000..d277ee9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/WmlToHtmlConverter02.cs
@@ -0,0 +1,160 @@
+/***************************************************************************
+
+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.Drawing.Imaging;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Xml.Linq;
+using DocumentFormat.OpenXml.Packaging;
+using OpenXmlPowerTools;
+using System.Collections.Generic;
+
+class WmlToHtmlConverterHelper
+{
+ static void Main(string[] args)
+ {
+ var n = DateTime.Now;
+ var tempDi = new DirectoryInfo(string.Format("ExampleOutput-{0:00}-{1:00}-{2:00}-{3:00}{4:00}{5:00}", n.Year - 2000, n.Month, n.Day, n.Hour, n.Minute, n.Second));
+ tempDi.Create();
+
+ /*
+ * This example loads each document into a byte array, then into a memory stream, so that the document can be opened for writing without
+ * modifying the source document.
+ */
+ foreach (var file in Directory.GetFiles("../../", "*.docx"))
+ {
+ ConvertToHtml(file, tempDi.FullName);
+ }
+ }
+
+ public static void ConvertToHtml(string file, string outputDirectory)
+ {
+ var fi = new FileInfo(file);
+ Console.WriteLine(fi.Name);
+ byte[] byteArray = File.ReadAllBytes(fi.FullName);
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ memoryStream.Write(byteArray, 0, byteArray.Length);
+ using (WordprocessingDocument wDoc = WordprocessingDocument.Open(memoryStream, true))
+ {
+ var destFileName = new FileInfo(fi.Name.Replace(".docx", ".html"));
+ if (outputDirectory != null && outputDirectory != string.Empty)
+ {
+ DirectoryInfo di = new DirectoryInfo(outputDirectory);
+ if (!di.Exists)
+ {
+ throw new OpenXmlPowerToolsException("Output directory does not exist");
+ }
+ destFileName = new FileInfo(Path.Combine(di.FullName, destFileName.Name));
+ }
+ var imageDirectoryName = destFileName.FullName.Substring(0, destFileName.FullName.Length - 5) + "_files";
+ int imageCounter = 0;
+
+ var pageTitle = fi.FullName;
+ var part = wDoc.CoreFilePropertiesPart;
+ if (part != null)
+ {
+ pageTitle = (string)part.GetXDocument().Descendants(DC.title).FirstOrDefault() ?? fi.FullName;
+ }
+
+ // TODO: Determine max-width from size of content area.
+ WmlToHtmlConverterSettings settings = new WmlToHtmlConverterSettings()
+ {
+ AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
+ PageTitle = pageTitle,
+ FabricateCssClasses = true,
+ CssClassPrefix = "pt-",
+ RestrictToSupportedLanguages = false,
+ RestrictToSupportedNumberingFormats = false,
+ ImageHandler = imageInfo =>
+ {
+ ++imageCounter;
+ string extension = imageInfo.ContentType.Split('/')[1].ToLower();
+ ImageFormat imageFormat = null;
+ if (extension == "png")
+ imageFormat = ImageFormat.Png;
+ else if (extension == "gif")
+ imageFormat = ImageFormat.Gif;
+ else if (extension == "bmp")
+ imageFormat = ImageFormat.Bmp;
+ else if (extension == "jpeg")
+ imageFormat = ImageFormat.Jpeg;
+ else if (extension == "tiff")
+ {
+ // Convert tiff to gif.
+ extension = "gif";
+ imageFormat = ImageFormat.Gif;
+ }
+ else if (extension == "x-wmf")
+ {
+ extension = "wmf";
+ imageFormat = ImageFormat.Wmf;
+ }
+
+ // If the image format isn't one that we expect, ignore it,
+ // and don't return markup for the link.
+ if (imageFormat == null)
+ return null;
+
+ string base64 = null;
+ try
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ imageInfo.Bitmap.Save(ms, imageFormat);
+ var ba = ms.ToArray();
+ base64 = System.Convert.ToBase64String(ba);
+ }
+ }
+ catch (System.Runtime.InteropServices.ExternalException)
+ {
+ return null;
+ }
+
+ ImageFormat format = imageInfo.Bitmap.RawFormat;
+ ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == format.Guid);
+ string mimeType = codec.MimeType;
+
+ string imageSource = string.Format("data:{0};base64,{1}", mimeType, base64);
+
+ XElement img = new XElement(Xhtml.img,
+ new XAttribute(NoNamespace.src, imageSource),
+ imageInfo.ImgStyleAttribute,
+ imageInfo.AltText != null ?
+ new XAttribute(NoNamespace.alt, imageInfo.AltText) : null);
+ return img;
+ }
+ };
+ XElement htmlElement = WmlToHtmlConverter.ConvertToHtml(wDoc, settings);
+
+ // Produce HTML document with <!DOCTYPE html > declaration to tell the browser
+ // we are using HTML5.
+ var html = new XDocument(
+ new XDocumentType("html", null, null, null),
+ htmlElement);
+
+ // Note: the xhtml returned by ConvertToHtmlTransform contains objects of type
+ // XEntity. PtOpenXmlUtil.cs define the XEntity class. See
+ // http://blogs.msdn.com/ericwhite/archive/2010/01/21/writing-entity-references-using-linq-to-xml.aspx
+ // for detailed explanation.
+ //
+ // If you further transform the XML tree returned by ConvertToHtmlTransform, you
+ // must do it correctly, or entities will not be serialized properly.
+
+ var htmlString = html.ToString(SaveOptions.DisableFormatting);
+ File.WriteAllText(destFileName.FullName, htmlString, Encoding.UTF8);
+ }
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/WmlToHtmlConverter02.csproj b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/WmlToHtmlConverter02.csproj
new file mode 100644
index 0000000..fbc94d9
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WmlToHtmlConverter02/WmlToHtmlConverter02.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.8.1" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/OpenXmlPowerToolsExamples/WordAutomationUtilities/WordAutomationUtilities.cs b/OpenXmlPowerToolsExamples/WordAutomationUtilities/WordAutomationUtilities.cs
new file mode 100644
index 0000000..b4f54c1
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WordAutomationUtilities/WordAutomationUtilities.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Word = Microsoft.Office.Interop.Word;
+
+namespace OpenXmlPowerTools
+{
+ public class WordAutomationUtilities
+ {
+ public static void ProcessFilesUsingWordAutomation(List<string> fileNames)
+ {
+ Word.Application app = new Word.Application();
+ app.Visible = false;
+ foreach (string fileName in fileNames)
+ {
+ FileInfo fi = new FileInfo(fileName);
+ try
+ {
+ Word.Document doc = app.Documents.Open(fi.FullName);
+ doc.Save();
+ }
+ catch (System.Runtime.InteropServices.COMException)
+ {
+ Console.WriteLine("Caught unexpected COM exception.");
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ Environment.Exit(0);
+ }
+ }
+ ((Microsoft.Office.Interop.Word._Application)app).Quit();
+ }
+ }
+}
diff --git a/OpenXmlPowerToolsExamples/WordAutomationUtilities/WordAutomationUtilities.csproj b/OpenXmlPowerToolsExamples/WordAutomationUtilities/WordAutomationUtilities.csproj
new file mode 100644
index 0000000..d1fe6d3
--- /dev/null
+++ b/OpenXmlPowerToolsExamples/WordAutomationUtilities/WordAutomationUtilities.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net45</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\OpenXmlPowerTools\OpenXmlPowerTools.csproj" />
+ <PackageReference Include="DocumentFormat.OpenXml" Version="2.7.1" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f356b43
--- /dev/null
+++ b/README.md
@@ -0,0 +1,198 @@
+[](https://ci.appveyor.com/project/openxmlsdk/open-xml-powertools)
+
+Open-XML-PowerTools
+===================
+The Open XML PowerTools provides guidance and example code for programming with Open XML
+Documents (DOCX, XLSX, and PPTX). It is based on, and extends the functionality
+of the [Open XML SDK](https://github.com/OfficeDev/Open-XML-SDK).
+
+It supports scenarios such as:
+- Splitting DOCX/PPTX files into multiple files.
+- Combining multiple DOCX/PPTX files into a single file.
+- Populating content in template DOCX files with data from XML.
+- High-fidelity conversion of DOCX to HTML/CSS.
+- High-fidelity conversion of HTML/CSS to DOCX.
+- Searching and replacing content in DOCX/PPTX using regular expressions.
+- Managing tracked-revisions, including detecting tracked revisions, and accepting tracked revisions.
+- Updating Charts in DOCX/PPTX files, including updating cached data, as well as the embedded XLSX.
+- Comparing two DOCX files, producing a DOCX with revision tracking markup, and enabling retrieving a list of revisions.
+- Retrieving metrics from DOCX files, including the hierarchy of styles used, the languages used, and the fonts used.
+- Writing XLSX files using far simpler code than directly writing the markup, including a streaming approach that
+ enables writing XLSX files with millions of rows.
+- Extracting data (along with formatting) from spreadsheets.
+
+Copyright (c) Microsoft Corporation 2012-2017
+Portions Copyright (c) Eric White 2016-2017
+Licensed under the Microsoft Public License.
+See License.txt in the project root for license information.
+
+News
+====
+New Release! Version 4.4.
+
+This version has a completely re-written WmlComparer.cs, which now supports nested tables and text boxes. WmlComparer.cs is a module that compares two DOCX files and
+produces a DOCX with revision tracking markup. It enables retrieving a list of revisions.
+
+Open-Xml-PowerTools Content
+===========================
+
+There is a lot of content about Open-Xml-PowerTools at the [Open-Xml-PowerTools Resource Center at OpenXmlDeveloper.org](http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx)
+
+See:
+- [DocumentBuilder Resource Center](http://openxmldeveloper.org/wiki/w/wiki/documentbuilder.aspx)
+- [PresentationBuilder Resource Center](http://openxmldeveloper.org/wiki/w/wiki/presentationbuilder.aspx)
+- [HtmlConverter Resource Center](http://openxmldeveloper.org/wiki/w/wiki/htmlconverter.aspx)
+- [Introduction to DocumentAssembler](https://www.youtube.com/watch?v=9QqzCgfqA2Y)
+- [Contributing to Open-Xml-PowerTools via GitHub](https://www.youtube.com/watch?v=Ii7z9L6Dkko)
+- [Gitting, Building, and Installing Open-Xml-PowerTools](https://www.youtube.com/watch?v=60w-yPDSQD0)
+
+Build Instructions
+==================
+
+**Prerequisites:**
+
+- Visual Studio 2017 Update 5 or .NET CLI toolchain
+
+**Build**
+
+ With Visual Studio:
+
+- Open `OpenXmlPowerTools.sln` in Visual Studio
+- Rebuild the project
+- Build the solution. To validate the build, open the Test Explorer. Click Run All.
+- To run an example, set the example as the startup project, and press F5.
+
+With .NET CLI toolchain:
+
+- Run `dotnet build OpenXmlPowerTools.sln`
+
+Change Log
+==========
+
+Version 4.3 : June 13, 2016
+- New WmlComparer module
+
+Version 4.2 : December 11, 2015
+- New SmlDataRetriever module
+- New SmlCellFormatter module
+
+Version 4.1.3 : November 2, 2015
+- DocumentAssembler: Fix bug associated with duplicate bookmarks.
+- DocumentAssembler: Enable processing of content controls / metadata in footer rows.
+- DocumentAssembler: Avoid processing content controls used for purposes other than the DocumentAssembler template, including page numbers in footers, etc.
+
+Version 4.1.2 : October 31, 2015
+- HtmlToWmlConverter: Handle unknown elements by recursively processing descendants
+
+Version 4.1.1 : October 21, 2015
+- Fix to AddTypes.ps1 to compile WmlToHtmlConverter.cs instead of HtmlConverter.cs
+- Fix to MettricsGetter.ps1 to correctly report whether a document contains tracked revisions
+- Added some unit tests for PresentationBuilder
+
+Version 4.1.0 : September 27, 2015
+- New HtmlToWmlConverter module
+- HtmlConverter generates non breaking spaces as #00a0 unicode charater, not entity.
+
+Version 4.0.0 : August 6, 2015
+- New DocumentAssember module
+- New SpreadsheetWriter module
+- New Cmdlet: Complete-DocxTemplateFromXml
+- Fix DocumentBuilder: deal with headers / footers more rationally
+- Enhance DocumentBuilder: add option to discard headers / footers from section (but keep layout of section)
+- Fix RevisionAccepter: deal with w:moveTo immediately before a table
+- New test document library in the TestFiles directory
+- XUnit tests
+- Cleaned up build system
+- Build using the open source Open-Xml-SDK and the new System.IO.Packaging by default
+- Back port to .NET 3.5
+- Rename the PowerShell module to Open-Xml-PowerTools
+
+Version 3.1.11 : June 30, 2015
+- Updated projects and solutions to build with the open source Open XML SDK and new System.IO.Packaging
+
+Version 3.1.10 : June 14, 2015
+- Changed Out-Xlsx Cmdlet to C# implementation
+- Fix Add-DocxText
+
+Version 3.1.09 : April 20, 2015
+- Fix OpenXmlRegex: PowerPoint 2007 and xml:space issues, causing 2007 to not open PPTX's
+
+Version 3.1.08 : March 13, 2015
+- Added Out-Xlsx Cmdlet
+
+Version 3.1.07 : February 9, 2015
+- Added Merge-Pptx Cmdlet
+- Added New-Pptx Cmdlet
+- Added New-PmlDocument
+- Fixed help for Merge-Docx
+- Don't throw duplicate attribute exception when running FormattingAssembler.AssembleFormatting
+ twice on same document.
+
+Version 3.1.06 : February 7, 2015
+- Added Expand-DocxFormatting Cmdlet
+- Cmdlets do not keep a handle to the current directory, preventing deletion of the directory.
+- Added additional tests to Test-OxPtCmdlets
+
+Version 3.1.05 : January 29, 2015
+- Added GetListItemText_zh_CN.cs
+- Fixed GetListItemText_fr_FR.cs
+- Partially fixed GetListItemText_ru_RU.cs
+- Fixed GetListItemText_Default.cs
+- Added better support in ListItemRetriever.cs
+- Added FileUtils class in PtUtil.cs
+
+Version 3.1.04 : December 17, 2014
+- Added Get-DocxMetrics Cmdlet
+- Added New-WmlDocument Cmdlet
+- Added MetricsGetter.cs module
+- Added MettricsGetter01.cs module, along with sample documents
+- Reworked Add-DocxText, new style of using it with New-WmlDocument
+
+Version 3.1.03 : December 9, 2014
+- Added ChartUpdater.cs module
+- Added ChartUpdater01.cs module, along with sample documents
+- Added Test-OxPtCmdlets Cmdlet
+
+Version 3.1.02 : December 1, 2014
+- Added Add-DocxText Cmdlet
+
+Version 3.1.01 : November 23, 2014
+- Added Convert-DocxToHtml Cmdlet
+- Added Chinese and Hebrew sample documents
+- Cmdlets in this release
+ Clear-DocxTrackedRevision
+ Convert-DocxToHtml
+ ConvertFrom-Base64
+ ConvertFrom-FlatOpc
+ ConvertTo-Base64
+ ConvertTo-FlatOpc
+ Get-OpenXmlValidationErrors
+ Merge-Docx
+ New-Docx
+ Test-OpenXmlValid
+
+Version 3.1.00 : November 13, 2014
+- Changed installation process - no longer requires compilation using Visual Studio
+- Added ConvertTo-FlatOpc Cmdlet
+- Added ConvertFrom-FlatOpc Cmdlet
+- Changed parameters for Test-OpenXmlValid, Get-OpenXmlValidationErrors
+- Removed the unnecessary 1/2 second sleep when doing Word automation in the New-Docx Cmdlet
+
+Version 3.0.00 : October 29, 2014
+- New release of cmdlets that are written as 'Advanced Functions' instead of in C#.
+
+Procedures for enhancing Open-Xml-PowerTools
+--------------------------------------------
+There are a variety of things to do when adding a new CmdLet to Open-Xml-PowerTools:
+- Write the new CmdLet. Put it in the Cmdlets directory
+- Modify Open-Xml-PowerTools.psm1
+ - Call the new Cmdlet script to make the function available
+ - Modify Export-ModuleMember function to export the Cmdlet and any aliases
+- Update Readme.txt, describing the enhancement
+- Add a new test to Test-OpenXmlPowerToolsCmdlets.ps1
+
+Procedures for enhancing the core C# modules
+- Modify the code
+- Write xUnit tests
+- Write an example if necessary
+- Run xUnit tests
diff --git a/TestFiles/Blank-altChunk.docx b/TestFiles/Blank-altChunk.docx
new file mode 100644
index 0000000..f70b598
--- /dev/null
+++ b/TestFiles/Blank-altChunk.docx
Binary files differ
diff --git a/TestFiles/Blank-wml.docx b/TestFiles/Blank-wml.docx
new file mode 100644
index 0000000..92ba10a
--- /dev/null
+++ b/TestFiles/Blank-wml.docx
Binary files differ
diff --git a/TestFiles/CA/CA001-Plain-COMPARE-CA001-Plain-Mod.docx b/TestFiles/CA/CA001-Plain-COMPARE-CA001-Plain-Mod.docx
new file mode 100644
index 0000000..e767818
--- /dev/null
+++ b/TestFiles/CA/CA001-Plain-COMPARE-CA001-Plain-Mod.docx
Binary files differ
diff --git a/TestFiles/CA/CA001-Plain-Mod.docx b/TestFiles/CA/CA001-Plain-Mod.docx
new file mode 100644
index 0000000..3039ee5
--- /dev/null
+++ b/TestFiles/CA/CA001-Plain-Mod.docx
Binary files differ
diff --git a/TestFiles/CA/CA001-Plain.docx b/TestFiles/CA/CA001-Plain.docx
new file mode 100644
index 0000000..c4991e6
--- /dev/null
+++ b/TestFiles/CA/CA001-Plain.docx
Binary files differ
diff --git a/TestFiles/CA/CA002-Bookmark.docx b/TestFiles/CA/CA002-Bookmark.docx
new file mode 100644
index 0000000..13a4369
--- /dev/null
+++ b/TestFiles/CA/CA002-Bookmark.docx
Binary files differ
diff --git a/TestFiles/CA/CA003-Numbered-List.docx b/TestFiles/CA/CA003-Numbered-List.docx
new file mode 100644
index 0000000..6183db3
--- /dev/null
+++ b/TestFiles/CA/CA003-Numbered-List.docx
Binary files differ
diff --git a/TestFiles/CA/CA004-TwoParas.docx b/TestFiles/CA/CA004-TwoParas.docx
new file mode 100644
index 0000000..d3d879e
--- /dev/null
+++ b/TestFiles/CA/CA004-TwoParas.docx
Binary files differ
diff --git a/TestFiles/CA/CA005-Table.docx b/TestFiles/CA/CA005-Table.docx
new file mode 100644
index 0000000..1f2cd6f
--- /dev/null
+++ b/TestFiles/CA/CA005-Table.docx
Binary files differ
diff --git a/TestFiles/CA/CA006-ContentControl.docx b/TestFiles/CA/CA006-ContentControl.docx
new file mode 100644
index 0000000..e67417d
--- /dev/null
+++ b/TestFiles/CA/CA006-ContentControl.docx
Binary files differ
diff --git a/TestFiles/CA/CA007-DayLong.docx b/TestFiles/CA/CA007-DayLong.docx
new file mode 100644
index 0000000..34b253f
--- /dev/null
+++ b/TestFiles/CA/CA007-DayLong.docx
Binary files differ
diff --git a/TestFiles/CA/CA008-Footnote-Reference.docx b/TestFiles/CA/CA008-Footnote-Reference.docx
new file mode 100644
index 0000000..ba2dcc3
--- /dev/null
+++ b/TestFiles/CA/CA008-Footnote-Reference.docx
Binary files differ
diff --git a/TestFiles/CA/CA009-altChunk.docx b/TestFiles/CA/CA009-altChunk.docx
new file mode 100644
index 0000000..efe439d
--- /dev/null
+++ b/TestFiles/CA/CA009-altChunk.docx
Binary files differ
diff --git a/TestFiles/CA/CA010-Delete-Run.docx b/TestFiles/CA/CA010-Delete-Run.docx
new file mode 100644
index 0000000..4f70bb2
--- /dev/null
+++ b/TestFiles/CA/CA010-Delete-Run.docx
Binary files differ
diff --git a/TestFiles/CA/CA011-Insert-Run.docx b/TestFiles/CA/CA011-Insert-Run.docx
new file mode 100644
index 0000000..93c83ed
--- /dev/null
+++ b/TestFiles/CA/CA011-Insert-Run.docx
Binary files differ
diff --git a/TestFiles/CA/CA012-fldSimple.docx b/TestFiles/CA/CA012-fldSimple.docx
new file mode 100644
index 0000000..69305db
--- /dev/null
+++ b/TestFiles/CA/CA012-fldSimple.docx
Binary files differ
diff --git a/TestFiles/CA/CA013-Lots-of-Stuff.docx b/TestFiles/CA/CA013-Lots-of-Stuff.docx
new file mode 100644
index 0000000..42b81da
--- /dev/null
+++ b/TestFiles/CA/CA013-Lots-of-Stuff.docx
Binary files differ
diff --git a/TestFiles/CA/CA014-Complex-Table.docx b/TestFiles/CA/CA014-Complex-Table.docx
new file mode 100644
index 0000000..5282b9b
--- /dev/null
+++ b/TestFiles/CA/CA014-Complex-Table.docx
Binary files differ
diff --git a/TestFiles/CU001-Chart-Cached-Data-01.docx b/TestFiles/CU001-Chart-Cached-Data-01.docx
new file mode 100644
index 0000000..2cb7a30
--- /dev/null
+++ b/TestFiles/CU001-Chart-Cached-Data-01.docx
Binary files differ
diff --git a/TestFiles/CU002-Chart-Cached-Data-02.docx b/TestFiles/CU002-Chart-Cached-Data-02.docx
new file mode 100644
index 0000000..60abe27
--- /dev/null
+++ b/TestFiles/CU002-Chart-Cached-Data-02.docx
Binary files differ
diff --git a/TestFiles/CU003-Chart-Cached-Data-03.docx b/TestFiles/CU003-Chart-Cached-Data-03.docx
new file mode 100644
index 0000000..dd95819
--- /dev/null
+++ b/TestFiles/CU003-Chart-Cached-Data-03.docx
Binary files differ
diff --git a/TestFiles/CU004-Chart-Cached-Data-04.docx b/TestFiles/CU004-Chart-Cached-Data-04.docx
new file mode 100644
index 0000000..582edac
--- /dev/null
+++ b/TestFiles/CU004-Chart-Cached-Data-04.docx
Binary files differ
diff --git a/TestFiles/CU005-Chart-Cached-Data-05.docx b/TestFiles/CU005-Chart-Cached-Data-05.docx
new file mode 100644
index 0000000..ca19068
--- /dev/null
+++ b/TestFiles/CU005-Chart-Cached-Data-05.docx
Binary files differ
diff --git a/TestFiles/CU006-Chart-Cached-Data-06.docx b/TestFiles/CU006-Chart-Cached-Data-06.docx
new file mode 100644
index 0000000..93d591a
--- /dev/null
+++ b/TestFiles/CU006-Chart-Cached-Data-06.docx
Binary files differ
diff --git a/TestFiles/CU007-Chart-Cached-Data-07.docx b/TestFiles/CU007-Chart-Cached-Data-07.docx
new file mode 100644
index 0000000..cca887d
--- /dev/null
+++ b/TestFiles/CU007-Chart-Cached-Data-07.docx
Binary files differ
diff --git a/TestFiles/CU008-Chart-Cached-Data-08.docx b/TestFiles/CU008-Chart-Cached-Data-08.docx
new file mode 100644
index 0000000..428f74e
--- /dev/null
+++ b/TestFiles/CU008-Chart-Cached-Data-08.docx
Binary files differ
diff --git a/TestFiles/CU009-Chart-Embedded-Xlsx-01.docx b/TestFiles/CU009-Chart-Embedded-Xlsx-01.docx
new file mode 100644
index 0000000..e813a05
--- /dev/null
+++ b/TestFiles/CU009-Chart-Embedded-Xlsx-01.docx
Binary files differ
diff --git a/TestFiles/CU010-Chart-Embedded-Xlsx-02.docx b/TestFiles/CU010-Chart-Embedded-Xlsx-02.docx
new file mode 100644
index 0000000..704bbef
--- /dev/null
+++ b/TestFiles/CU010-Chart-Embedded-Xlsx-02.docx
Binary files differ
diff --git a/TestFiles/CU011-Chart-Embedded-Xlsx-03.docx b/TestFiles/CU011-Chart-Embedded-Xlsx-03.docx
new file mode 100644
index 0000000..c9c8c62
--- /dev/null
+++ b/TestFiles/CU011-Chart-Embedded-Xlsx-03.docx
Binary files differ
diff --git a/TestFiles/CU012-Chart-Embedded-Xlsx-04.docx b/TestFiles/CU012-Chart-Embedded-Xlsx-04.docx
new file mode 100644
index 0000000..d1578bc
--- /dev/null
+++ b/TestFiles/CU012-Chart-Embedded-Xlsx-04.docx
Binary files differ
diff --git a/TestFiles/CU013-Chart-Embedded-Xlsx-05.docx b/TestFiles/CU013-Chart-Embedded-Xlsx-05.docx
new file mode 100644
index 0000000..514befd
--- /dev/null
+++ b/TestFiles/CU013-Chart-Embedded-Xlsx-05.docx
Binary files differ
diff --git a/TestFiles/CU014-Chart-Embedded-Xlsx-06.docx b/TestFiles/CU014-Chart-Embedded-Xlsx-06.docx
new file mode 100644
index 0000000..ad76f7d
--- /dev/null
+++ b/TestFiles/CU014-Chart-Embedded-Xlsx-06.docx
Binary files differ
diff --git a/TestFiles/CU015-Chart-Embedded-Xlsx-07.docx b/TestFiles/CU015-Chart-Embedded-Xlsx-07.docx
new file mode 100644
index 0000000..7132102
--- /dev/null
+++ b/TestFiles/CU015-Chart-Embedded-Xlsx-07.docx
Binary files differ
diff --git a/TestFiles/CU016-Chart-Embedded-Xlsx-08.docx b/TestFiles/CU016-Chart-Embedded-Xlsx-08.docx
new file mode 100644
index 0000000..f4e3d32
--- /dev/null
+++ b/TestFiles/CU016-Chart-Embedded-Xlsx-08.docx
Binary files differ
diff --git a/TestFiles/CU017-Chart-Embedded-Xlsx-10.docx b/TestFiles/CU017-Chart-Embedded-Xlsx-10.docx
new file mode 100644
index 0000000..7560ba4
--- /dev/null
+++ b/TestFiles/CU017-Chart-Embedded-Xlsx-10.docx
Binary files differ
diff --git a/TestFiles/CU018-Chart-Cached-Data-41.pptx b/TestFiles/CU018-Chart-Cached-Data-41.pptx
new file mode 100644
index 0000000..6e80384
--- /dev/null
+++ b/TestFiles/CU018-Chart-Cached-Data-41.pptx
Binary files differ
diff --git a/TestFiles/CU019-Chart-Embedded-Xlsx-41.pptx b/TestFiles/CU019-Chart-Embedded-Xlsx-41.pptx
new file mode 100644
index 0000000..3796212
--- /dev/null
+++ b/TestFiles/CU019-Chart-Embedded-Xlsx-41.pptx
Binary files differ
diff --git a/TestFiles/CZ/CZ001-Plain-Mod.docx b/TestFiles/CZ/CZ001-Plain-Mod.docx
new file mode 100644
index 0000000..782bd72
--- /dev/null
+++ b/TestFiles/CZ/CZ001-Plain-Mod.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ001-Plain.docx b/TestFiles/CZ/CZ001-Plain.docx
new file mode 100644
index 0000000..c4991e6
--- /dev/null
+++ b/TestFiles/CZ/CZ001-Plain.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ002-Multi-Paragraphs-Mod.docx b/TestFiles/CZ/CZ002-Multi-Paragraphs-Mod.docx
new file mode 100644
index 0000000..138e5b8
--- /dev/null
+++ b/TestFiles/CZ/CZ002-Multi-Paragraphs-Mod.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ002-Multi-Paragraphs.docx b/TestFiles/CZ/CZ002-Multi-Paragraphs.docx
new file mode 100644
index 0000000..5d6dab5
--- /dev/null
+++ b/TestFiles/CZ/CZ002-Multi-Paragraphs.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ003-Multi-Paragraphs-Mod.docx b/TestFiles/CZ/CZ003-Multi-Paragraphs-Mod.docx
new file mode 100644
index 0000000..437cd0e
--- /dev/null
+++ b/TestFiles/CZ/CZ003-Multi-Paragraphs-Mod.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ003-Multi-Paragraphs.docx b/TestFiles/CZ/CZ003-Multi-Paragraphs.docx
new file mode 100644
index 0000000..3161def
--- /dev/null
+++ b/TestFiles/CZ/CZ003-Multi-Paragraphs.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ004-Multi-Paragraphs-in-Cell-Mod.docx b/TestFiles/CZ/CZ004-Multi-Paragraphs-in-Cell-Mod.docx
new file mode 100644
index 0000000..af1d2f4
--- /dev/null
+++ b/TestFiles/CZ/CZ004-Multi-Paragraphs-in-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/CZ/CZ004-Multi-Paragraphs-in-Cell.docx b/TestFiles/CZ/CZ004-Multi-Paragraphs-in-Cell.docx
new file mode 100644
index 0000000..69a7cb3
--- /dev/null
+++ b/TestFiles/CZ/CZ004-Multi-Paragraphs-in-Cell.docx
Binary files differ
diff --git a/TestFiles/DA-CellDataInAttributes.xml b/TestFiles/DA-CellDataInAttributes.xml
new file mode 100644
index 0000000..0f06789
--- /dev/null
+++ b/TestFiles/DA-CellDataInAttributes.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order ProductDescription="Unicycle" Quantity="8" OrderDate="9/5/2001" />
+ <Order ProductDescription="Tricycle" Quantity="5" OrderDate="10/21/2005" />
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-ConditionalOnAttribute.xml b/TestFiles/DA-ConditionalOnAttribute.xml
new file mode 100644
index 0000000..c153382
--- /dev/null
+++ b/TestFiles/DA-ConditionalOnAttribute.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer HighValueCustomer="True">
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-Content-List.xml b/TestFiles/DA-Content-List.xml
new file mode 100644
index 0000000..af4281b
--- /dev/null
+++ b/TestFiles/DA-Content-List.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Data>
+ <Row>
+ <Link>http://blogs.msdn.com/b/ericwhite/archive/2007/12/11/openxml-content-types-in-an-xml-document.aspx</Link>
+ <Title>OpenXML Content Types in an XML Document</Title>
+ <Summary>Sometimes you need to programmatically access a variety of parts in Open XML documents based on the content types of the parts. In this case it is helpful to have a list of the content types in a programmatically accessible form.</Summary>
+ <Author>Eric White</Author>
+ <Keyword>OPC</Keyword>
+ <ArticlePages>1</ArticlePages>
+ <Code>None</Code>
+ <TechnicalLevel>100</TechnicalLevel>
+ <MediaType>Article</MediaType>
+ </Row>
+ <Row>
+ <Link>http://blogs.msdn.com/b/ericwhite/archive/2008/01/17/how-to-extract-comments-from-open-xml-documents.aspx</Link>
+ <Title>How to Extract Comments from Open XML Documents</Title>
+ <Summary>How to Extract Comments from Open XML Documents</Summary>
+ <Author>Eric White</Author>
+ <Keyword>Open XML SDK, WordprocessingML</Keyword>
+ <ArticlePages>5</ArticlePages>
+ <Code>C#</Code>
+ <TechnicalLevel>300</TechnicalLevel>
+ <MediaType>Article</MediaType>
+ </Row>
+ <Row>
+ <Link>http://blogs.msdn.com/b/ericwhite/archive/2008/06/11/processing-open-xml-documents-server-side-using-powershell.aspx</Link>
+ <Title>Automated Processing of Open XML Documents using PowerShell</Title>
+ <Summary>Processing Open XML documents using PowerShell is a powerful approach for creating, modifying, and transforming Open XML documents. The PowerTools for Open XML are examples and guidance that show how to do this. They consist of PowerShell cmdlets, and a number of example scripts that demonstrate the use of the cmdlets. Examples include automated word processing document and spreadsheet generation, and preparing documents for distribution external to a company, including removing comments, accepting revisions, applying a uniform theme to them, and applying a watermark to them.</Summary>
+ <Author>Eric White</Author>
+ <Keyword>PowerTools, DOCX Generation, XLSX Generation, Tools, Open XML SDK</Keyword>
+ <ArticlePages>4</ArticlePages>
+ <Code>None</Code>
+ <TechnicalLevel>200</TechnicalLevel>
+ <MediaType>Article</MediaType>
+ </Row>
+</Data>
\ No newline at end of file
diff --git a/TestFiles/DA-Data.xml b/TestFiles/DA-Data.xml
new file mode 100644
index 0000000..9af9218
--- /dev/null
+++ b/TestFiles/DA-Data.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+ <TotalQuantity>6</TotalQuantity>
+ <Description><![CDATA[This
+is a multiline
+description that
+contains details about
+Cheryl.]]></Description>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-DataNestedRepeat.xml b/TestFiles/DA-DataNestedRepeat.xml
new file mode 100644
index 0000000..80de767
--- /dev/null
+++ b/TestFiles/DA-DataNestedRepeat.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ <Items>
+ <Item Value="1" />
+ <Item Value="2" />
+ <Item Value="3" />
+ </Items>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ <Items>
+ <Item Value="4" />
+ <Item Value="5" />
+ <Item Value="6" />
+ </Items>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-DataNotHighValueCust.xml b/TestFiles/DA-DataNotHighValueCust.xml
new file mode 100644
index 0000000..b41d588
--- /dev/null
+++ b/TestFiles/DA-DataNotHighValueCust.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>False</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-DataSmallCustomer.xml b/TestFiles/DA-DataSmallCustomer.xml
new file mode 100644
index 0000000..7eed3d7
--- /dev/null
+++ b/TestFiles/DA-DataSmallCustomer.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>False</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+ <TotalQuantity>6</TotalQuantity>
+ <Description><![CDATA[This
+is a multiline
+description that
+contains details about
+Cheryl.]]></Description>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-TooMuchDataForCell.xml b/TestFiles/DA-TooMuchDataForCell.xml
new file mode 100644
index 0000000..698f629
--- /dev/null
+++ b/TestFiles/DA-TooMuchDataForCell.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA-TooMuchDataForConditional.xml b/TestFiles/DA-TooMuchDataForConditional.xml
new file mode 100644
index 0000000..cd88ce9
--- /dev/null
+++ b/TestFiles/DA-TooMuchDataForConditional.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA001-TemplateDocument.docx b/TestFiles/DA001-TemplateDocument.docx
new file mode 100644
index 0000000..8d19ed3
--- /dev/null
+++ b/TestFiles/DA001-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA002-TemplateDocument.docx b/TestFiles/DA002-TemplateDocument.docx
new file mode 100644
index 0000000..c710b37
--- /dev/null
+++ b/TestFiles/DA002-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA003-Select-XPathFindsNoData.docx b/TestFiles/DA003-Select-XPathFindsNoData.docx
new file mode 100644
index 0000000..450768d
--- /dev/null
+++ b/TestFiles/DA003-Select-XPathFindsNoData.docx
Binary files differ
diff --git a/TestFiles/DA004-Data.xml b/TestFiles/DA004-Data.xml
new file mode 100644
index 0000000..50d3b8c
--- /dev/null
+++ b/TestFiles/DA004-Data.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <CustomerID>1</CustomerID>
+ <Name>Cheryl</Name>
+ <HighValueCustomer>True</HighValueCustomer>
+ <Orders>
+ <Order>
+ <ProductDescription>Unicycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>9/5/2001</OrderDate>
+ </Order>
+ <Order>
+ <ProductDescription>Tricycle</ProductDescription>
+ <Quantity>3</Quantity>
+ <OrderDate>8/6/2000</OrderDate>
+ </Order>
+ </Orders>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA004-Select-XPathFindsNoDataOptional.docx b/TestFiles/DA004-Select-XPathFindsNoDataOptional.docx
new file mode 100644
index 0000000..16e9745
--- /dev/null
+++ b/TestFiles/DA004-Select-XPathFindsNoDataOptional.docx
Binary files differ
diff --git a/TestFiles/DA005-SelectRowData-NoData.docx b/TestFiles/DA005-SelectRowData-NoData.docx
new file mode 100644
index 0000000..42d7a7c
--- /dev/null
+++ b/TestFiles/DA005-SelectRowData-NoData.docx
Binary files differ
diff --git a/TestFiles/DA006-SelectTestValue-NoData.docx b/TestFiles/DA006-SelectTestValue-NoData.docx
new file mode 100644
index 0000000..5761cc0
--- /dev/null
+++ b/TestFiles/DA006-SelectTestValue-NoData.docx
Binary files differ
diff --git a/TestFiles/DA007-SelectRepeatingData-NoData.docx b/TestFiles/DA007-SelectRepeatingData-NoData.docx
new file mode 100644
index 0000000..286f201
--- /dev/null
+++ b/TestFiles/DA007-SelectRepeatingData-NoData.docx
Binary files differ
diff --git a/TestFiles/DA008-TableElementWithNoTable.docx b/TestFiles/DA008-TableElementWithNoTable.docx
new file mode 100644
index 0000000..c18dc70
--- /dev/null
+++ b/TestFiles/DA008-TableElementWithNoTable.docx
Binary files differ
diff --git a/TestFiles/DA009-InvalidXPath.docx b/TestFiles/DA009-InvalidXPath.docx
new file mode 100644
index 0000000..5158f99
--- /dev/null
+++ b/TestFiles/DA009-InvalidXPath.docx
Binary files differ
diff --git a/TestFiles/DA010-InvalidXml.docx b/TestFiles/DA010-InvalidXml.docx
new file mode 100644
index 0000000..d0f3527
--- /dev/null
+++ b/TestFiles/DA010-InvalidXml.docx
Binary files differ
diff --git a/TestFiles/DA011-SchemaError.docx b/TestFiles/DA011-SchemaError.docx
new file mode 100644
index 0000000..a08402d
--- /dev/null
+++ b/TestFiles/DA011-SchemaError.docx
Binary files differ
diff --git a/TestFiles/DA012-OtherMarkupTypes.docx b/TestFiles/DA012-OtherMarkupTypes.docx
new file mode 100644
index 0000000..eff2b09
--- /dev/null
+++ b/TestFiles/DA012-OtherMarkupTypes.docx
Binary files differ
diff --git a/TestFiles/DA013-Runs.docx b/TestFiles/DA013-Runs.docx
new file mode 100644
index 0000000..3d38b8e
--- /dev/null
+++ b/TestFiles/DA013-Runs.docx
Binary files differ
diff --git a/TestFiles/DA014-TwoRuns-NoValuesSelected.docx b/TestFiles/DA014-TwoRuns-NoValuesSelected.docx
new file mode 100644
index 0000000..2dfe76c
--- /dev/null
+++ b/TestFiles/DA014-TwoRuns-NoValuesSelected.docx
Binary files differ
diff --git a/TestFiles/DA015-TwoRunsXmlExceptionInFirst.docx b/TestFiles/DA015-TwoRunsXmlExceptionInFirst.docx
new file mode 100644
index 0000000..db8718d
--- /dev/null
+++ b/TestFiles/DA015-TwoRunsXmlExceptionInFirst.docx
Binary files differ
diff --git a/TestFiles/DA016-TwoRunsSchemaErrorInSecond.docx b/TestFiles/DA016-TwoRunsSchemaErrorInSecond.docx
new file mode 100644
index 0000000..476b26b
--- /dev/null
+++ b/TestFiles/DA016-TwoRunsSchemaErrorInSecond.docx
Binary files differ
diff --git a/TestFiles/DA017-FiveRuns.docx b/TestFiles/DA017-FiveRuns.docx
new file mode 100644
index 0000000..1b793c3
--- /dev/null
+++ b/TestFiles/DA017-FiveRuns.docx
Binary files differ
diff --git a/TestFiles/DA018-SmartQuotes.docx b/TestFiles/DA018-SmartQuotes.docx
new file mode 100644
index 0000000..d53120a
--- /dev/null
+++ b/TestFiles/DA018-SmartQuotes.docx
Binary files differ
diff --git a/TestFiles/DA019-RunIsEntireParagraph.docx b/TestFiles/DA019-RunIsEntireParagraph.docx
new file mode 100644
index 0000000..9ef2d30
--- /dev/null
+++ b/TestFiles/DA019-RunIsEntireParagraph.docx
Binary files differ
diff --git a/TestFiles/DA020-TwoRunsAndNoOtherContent.docx b/TestFiles/DA020-TwoRunsAndNoOtherContent.docx
new file mode 100644
index 0000000..6e749b6
--- /dev/null
+++ b/TestFiles/DA020-TwoRunsAndNoOtherContent.docx
Binary files differ
diff --git a/TestFiles/DA021-NestedRepeat.docx b/TestFiles/DA021-NestedRepeat.docx
new file mode 100644
index 0000000..0243426
--- /dev/null
+++ b/TestFiles/DA021-NestedRepeat.docx
Binary files differ
diff --git a/TestFiles/DA022-InvalidXPath.docx b/TestFiles/DA022-InvalidXPath.docx
new file mode 100644
index 0000000..1d6e586
--- /dev/null
+++ b/TestFiles/DA022-InvalidXPath.docx
Binary files differ
diff --git a/TestFiles/DA023-RepeatWOEndRepeat.docx b/TestFiles/DA023-RepeatWOEndRepeat.docx
new file mode 100644
index 0000000..5d2b001
--- /dev/null
+++ b/TestFiles/DA023-RepeatWOEndRepeat.docx
Binary files differ
diff --git a/TestFiles/DA024-TrackedRevisions.docx b/TestFiles/DA024-TrackedRevisions.docx
new file mode 100644
index 0000000..96eec4d
--- /dev/null
+++ b/TestFiles/DA024-TrackedRevisions.docx
Binary files differ
diff --git a/TestFiles/DA025-TemplateDocument.docx b/TestFiles/DA025-TemplateDocument.docx
new file mode 100644
index 0000000..ffb926c
--- /dev/null
+++ b/TestFiles/DA025-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA026-InvalidRootXmlElement.docx b/TestFiles/DA026-InvalidRootXmlElement.docx
new file mode 100644
index 0000000..725e731
--- /dev/null
+++ b/TestFiles/DA026-InvalidRootXmlElement.docx
Binary files differ
diff --git a/TestFiles/DA027-XPathErrorInPara.docx b/TestFiles/DA027-XPathErrorInPara.docx
new file mode 100644
index 0000000..6dd87a7
--- /dev/null
+++ b/TestFiles/DA027-XPathErrorInPara.docx
Binary files differ
diff --git a/TestFiles/DA028-NoPrototypeRow.docx b/TestFiles/DA028-NoPrototypeRow.docx
new file mode 100644
index 0000000..b7cb2f8
--- /dev/null
+++ b/TestFiles/DA028-NoPrototypeRow.docx
Binary files differ
diff --git a/TestFiles/DA029-NoDataForCell.docx b/TestFiles/DA029-NoDataForCell.docx
new file mode 100644
index 0000000..6faa9fb
--- /dev/null
+++ b/TestFiles/DA029-NoDataForCell.docx
Binary files differ
diff --git a/TestFiles/DA030-TooMuchDataForCell.docx b/TestFiles/DA030-TooMuchDataForCell.docx
new file mode 100644
index 0000000..68ae73f
--- /dev/null
+++ b/TestFiles/DA030-TooMuchDataForCell.docx
Binary files differ
diff --git a/TestFiles/DA031-CellDataInAttributes.docx b/TestFiles/DA031-CellDataInAttributes.docx
new file mode 100644
index 0000000..12b880a
--- /dev/null
+++ b/TestFiles/DA031-CellDataInAttributes.docx
Binary files differ
diff --git a/TestFiles/DA032-TooMuchDataForConditional.docx b/TestFiles/DA032-TooMuchDataForConditional.docx
new file mode 100644
index 0000000..d8ee6ec
--- /dev/null
+++ b/TestFiles/DA032-TooMuchDataForConditional.docx
Binary files differ
diff --git a/TestFiles/DA033-ConditionalOnAttribute.docx b/TestFiles/DA033-ConditionalOnAttribute.docx
new file mode 100644
index 0000000..344982d
--- /dev/null
+++ b/TestFiles/DA033-ConditionalOnAttribute.docx
Binary files differ
diff --git a/TestFiles/DA034-HeaderFooter.docx b/TestFiles/DA034-HeaderFooter.docx
new file mode 100644
index 0000000..b9f604d
--- /dev/null
+++ b/TestFiles/DA034-HeaderFooter.docx
Binary files differ
diff --git a/TestFiles/DA035-SchemaErrorInRepeat.docx b/TestFiles/DA035-SchemaErrorInRepeat.docx
new file mode 100644
index 0000000..284f5e8
--- /dev/null
+++ b/TestFiles/DA035-SchemaErrorInRepeat.docx
Binary files differ
diff --git a/TestFiles/DA036-SchemaErrorInConditional.docx b/TestFiles/DA036-SchemaErrorInConditional.docx
new file mode 100644
index 0000000..cf66376
--- /dev/null
+++ b/TestFiles/DA036-SchemaErrorInConditional.docx
Binary files differ
diff --git a/TestFiles/DA100-TemplateDocument.docx b/TestFiles/DA100-TemplateDocument.docx
new file mode 100644
index 0000000..677d42b
--- /dev/null
+++ b/TestFiles/DA100-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA101-TemplateDocument.docx b/TestFiles/DA101-TemplateDocument.docx
new file mode 100644
index 0000000..32d8841
--- /dev/null
+++ b/TestFiles/DA101-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA102-TemplateDocument.docx b/TestFiles/DA102-TemplateDocument.docx
new file mode 100644
index 0000000..e4683ce
--- /dev/null
+++ b/TestFiles/DA102-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA201-TemplateDocument.docx b/TestFiles/DA201-TemplateDocument.docx
new file mode 100644
index 0000000..637cf35
--- /dev/null
+++ b/TestFiles/DA201-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA202-TemplateDocument.docx b/TestFiles/DA202-TemplateDocument.docx
new file mode 100644
index 0000000..3a44e78
--- /dev/null
+++ b/TestFiles/DA202-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA203-Select-XPathFindsNoData.docx b/TestFiles/DA203-Select-XPathFindsNoData.docx
new file mode 100644
index 0000000..3b26a4d
--- /dev/null
+++ b/TestFiles/DA203-Select-XPathFindsNoData.docx
Binary files differ
diff --git a/TestFiles/DA204-Select-XPathFindsNoDataOptional.docx b/TestFiles/DA204-Select-XPathFindsNoDataOptional.docx
new file mode 100644
index 0000000..044a0c6
--- /dev/null
+++ b/TestFiles/DA204-Select-XPathFindsNoDataOptional.docx
Binary files differ
diff --git a/TestFiles/DA205-SelectRowData-NoData.docx b/TestFiles/DA205-SelectRowData-NoData.docx
new file mode 100644
index 0000000..4114995
--- /dev/null
+++ b/TestFiles/DA205-SelectRowData-NoData.docx
Binary files differ
diff --git a/TestFiles/DA206-SelectTestValue-NoData.docx b/TestFiles/DA206-SelectTestValue-NoData.docx
new file mode 100644
index 0000000..81379e3
--- /dev/null
+++ b/TestFiles/DA206-SelectTestValue-NoData.docx
Binary files differ
diff --git a/TestFiles/DA207-SelectRepeatingData-NoData.docx b/TestFiles/DA207-SelectRepeatingData-NoData.docx
new file mode 100644
index 0000000..bf384f4
--- /dev/null
+++ b/TestFiles/DA207-SelectRepeatingData-NoData.docx
Binary files differ
diff --git a/TestFiles/DA208-TableElementWithNoTable.docx b/TestFiles/DA208-TableElementWithNoTable.docx
new file mode 100644
index 0000000..9c0cce7
--- /dev/null
+++ b/TestFiles/DA208-TableElementWithNoTable.docx
Binary files differ
diff --git a/TestFiles/DA209-InvalidXPath.docx b/TestFiles/DA209-InvalidXPath.docx
new file mode 100644
index 0000000..0806147
--- /dev/null
+++ b/TestFiles/DA209-InvalidXPath.docx
Binary files differ
diff --git a/TestFiles/DA210-InvalidXml.docx b/TestFiles/DA210-InvalidXml.docx
new file mode 100644
index 0000000..6817fe4
--- /dev/null
+++ b/TestFiles/DA210-InvalidXml.docx
Binary files differ
diff --git a/TestFiles/DA211-SchemaError.docx b/TestFiles/DA211-SchemaError.docx
new file mode 100644
index 0000000..5401201
--- /dev/null
+++ b/TestFiles/DA211-SchemaError.docx
Binary files differ
diff --git a/TestFiles/DA212-OtherMarkupTypes.docx b/TestFiles/DA212-OtherMarkupTypes.docx
new file mode 100644
index 0000000..798a15f
--- /dev/null
+++ b/TestFiles/DA212-OtherMarkupTypes.docx
Binary files differ
diff --git a/TestFiles/DA213-Runs.docx b/TestFiles/DA213-Runs.docx
new file mode 100644
index 0000000..d0940c0
--- /dev/null
+++ b/TestFiles/DA213-Runs.docx
Binary files differ
diff --git a/TestFiles/DA214-TwoRuns-NoValuesSelected.docx b/TestFiles/DA214-TwoRuns-NoValuesSelected.docx
new file mode 100644
index 0000000..b427d5a
--- /dev/null
+++ b/TestFiles/DA214-TwoRuns-NoValuesSelected.docx
Binary files differ
diff --git a/TestFiles/DA215-TwoRunsXmlExceptionInFirst.docx b/TestFiles/DA215-TwoRunsXmlExceptionInFirst.docx
new file mode 100644
index 0000000..327a29e
--- /dev/null
+++ b/TestFiles/DA215-TwoRunsXmlExceptionInFirst.docx
Binary files differ
diff --git a/TestFiles/DA216-TwoRunsSchemaErrorInSecond.docx b/TestFiles/DA216-TwoRunsSchemaErrorInSecond.docx
new file mode 100644
index 0000000..c52f0cb
--- /dev/null
+++ b/TestFiles/DA216-TwoRunsSchemaErrorInSecond.docx
Binary files differ
diff --git a/TestFiles/DA217-FiveRuns.docx b/TestFiles/DA217-FiveRuns.docx
new file mode 100644
index 0000000..d2f809a
--- /dev/null
+++ b/TestFiles/DA217-FiveRuns.docx
Binary files differ
diff --git a/TestFiles/DA218-SmartQuotes.docx b/TestFiles/DA218-SmartQuotes.docx
new file mode 100644
index 0000000..ecc1939
--- /dev/null
+++ b/TestFiles/DA218-SmartQuotes.docx
Binary files differ
diff --git a/TestFiles/DA219-RunIsEntireParagraph.docx b/TestFiles/DA219-RunIsEntireParagraph.docx
new file mode 100644
index 0000000..8fd0d22
--- /dev/null
+++ b/TestFiles/DA219-RunIsEntireParagraph.docx
Binary files differ
diff --git a/TestFiles/DA220-TwoRunsAndNoOtherContent.docx b/TestFiles/DA220-TwoRunsAndNoOtherContent.docx
new file mode 100644
index 0000000..2f89d0a
--- /dev/null
+++ b/TestFiles/DA220-TwoRunsAndNoOtherContent.docx
Binary files differ
diff --git a/TestFiles/DA221-NestedRepeat.docx b/TestFiles/DA221-NestedRepeat.docx
new file mode 100644
index 0000000..1bad76c
--- /dev/null
+++ b/TestFiles/DA221-NestedRepeat.docx
Binary files differ
diff --git a/TestFiles/DA222-InvalidXPath.docx b/TestFiles/DA222-InvalidXPath.docx
new file mode 100644
index 0000000..99bba8c
--- /dev/null
+++ b/TestFiles/DA222-InvalidXPath.docx
Binary files differ
diff --git a/TestFiles/DA223-RepeatWOEndRepeat.docx b/TestFiles/DA223-RepeatWOEndRepeat.docx
new file mode 100644
index 0000000..2864214
--- /dev/null
+++ b/TestFiles/DA223-RepeatWOEndRepeat.docx
Binary files differ
diff --git a/TestFiles/DA224-TrackedRevisions.docx b/TestFiles/DA224-TrackedRevisions.docx
new file mode 100644
index 0000000..4470afa
--- /dev/null
+++ b/TestFiles/DA224-TrackedRevisions.docx
Binary files differ
diff --git a/TestFiles/DA225-TemplateDocument.docx b/TestFiles/DA225-TemplateDocument.docx
new file mode 100644
index 0000000..2c76049
--- /dev/null
+++ b/TestFiles/DA225-TemplateDocument.docx
Binary files differ
diff --git a/TestFiles/DA226-InvalidRootXmlElement.docx b/TestFiles/DA226-InvalidRootXmlElement.docx
new file mode 100644
index 0000000..da08587
--- /dev/null
+++ b/TestFiles/DA226-InvalidRootXmlElement.docx
Binary files differ
diff --git a/TestFiles/DA227-XPathErrorInPara.docx b/TestFiles/DA227-XPathErrorInPara.docx
new file mode 100644
index 0000000..fe8d574
--- /dev/null
+++ b/TestFiles/DA227-XPathErrorInPara.docx
Binary files differ
diff --git a/TestFiles/DA228-NoPrototypeRow.docx b/TestFiles/DA228-NoPrototypeRow.docx
new file mode 100644
index 0000000..c31191c
--- /dev/null
+++ b/TestFiles/DA228-NoPrototypeRow.docx
Binary files differ
diff --git a/TestFiles/DA229-NoDataForCell.docx b/TestFiles/DA229-NoDataForCell.docx
new file mode 100644
index 0000000..05f48be
--- /dev/null
+++ b/TestFiles/DA229-NoDataForCell.docx
Binary files differ
diff --git a/TestFiles/DA230-TooMuchDataForCell.docx b/TestFiles/DA230-TooMuchDataForCell.docx
new file mode 100644
index 0000000..953460f
--- /dev/null
+++ b/TestFiles/DA230-TooMuchDataForCell.docx
Binary files differ
diff --git a/TestFiles/DA231-CellDataInAttributes.docx b/TestFiles/DA231-CellDataInAttributes.docx
new file mode 100644
index 0000000..6ce1c22
--- /dev/null
+++ b/TestFiles/DA231-CellDataInAttributes.docx
Binary files differ
diff --git a/TestFiles/DA232-TooMuchDataForConditional.docx b/TestFiles/DA232-TooMuchDataForConditional.docx
new file mode 100644
index 0000000..2432c30
--- /dev/null
+++ b/TestFiles/DA232-TooMuchDataForConditional.docx
Binary files differ
diff --git a/TestFiles/DA233-ConditionalOnAttribute.docx b/TestFiles/DA233-ConditionalOnAttribute.docx
new file mode 100644
index 0000000..54a4da1
--- /dev/null
+++ b/TestFiles/DA233-ConditionalOnAttribute.docx
Binary files differ
diff --git a/TestFiles/DA234-HeaderFooter.docx b/TestFiles/DA234-HeaderFooter.docx
new file mode 100644
index 0000000..03515ac
--- /dev/null
+++ b/TestFiles/DA234-HeaderFooter.docx
Binary files differ
diff --git a/TestFiles/DA235-Crashes.docx b/TestFiles/DA235-Crashes.docx
new file mode 100644
index 0000000..9d9c9cd
--- /dev/null
+++ b/TestFiles/DA235-Crashes.docx
Binary files differ
diff --git a/TestFiles/DA236-Page-Num-in-Footer.docx b/TestFiles/DA236-Page-Num-in-Footer.docx
new file mode 100644
index 0000000..f3cd0ca
--- /dev/null
+++ b/TestFiles/DA236-Page-Num-in-Footer.docx
Binary files differ
diff --git a/TestFiles/DA237-SchemaErrorInRepeat.docx b/TestFiles/DA237-SchemaErrorInRepeat.docx
new file mode 100644
index 0000000..093a528
--- /dev/null
+++ b/TestFiles/DA237-SchemaErrorInRepeat.docx
Binary files differ
diff --git a/TestFiles/DA238-SchemaErrorInConditional.docx b/TestFiles/DA238-SchemaErrorInConditional.docx
new file mode 100644
index 0000000..8e2145e
--- /dev/null
+++ b/TestFiles/DA238-SchemaErrorInConditional.docx
Binary files differ
diff --git a/TestFiles/DA239-RunLevelCC-Repeat.docx b/TestFiles/DA239-RunLevelCC-Repeat.docx
new file mode 100644
index 0000000..6ce6d80
--- /dev/null
+++ b/TestFiles/DA239-RunLevelCC-Repeat.docx
Binary files differ
diff --git a/TestFiles/DA250-Address.xml b/TestFiles/DA250-Address.xml
new file mode 100644
index 0000000..ffff01d
--- /dev/null
+++ b/TestFiles/DA250-Address.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Customer>
+ <FullNames>Mr David Jones & Mrs Jane Jones</FullNames>
+ <OrderValue>2300</OrderValue>
+ <CorrespondenceAddress>
+ <AddressOne>46 Park View</AddressOne>
+ <AddressTwo>Leeds</AddressTwo>
+ <AddressThree>West Yorkshire</AddressThree>
+ <AddressFour />
+ <AddressFive />
+ <PostCode>LS1 4AR</PostCode>
+ </CorrespondenceAddress>
+</Customer>
\ No newline at end of file
diff --git a/TestFiles/DA250-ConditionalWithRichXPath.docx b/TestFiles/DA250-ConditionalWithRichXPath.docx
new file mode 100644
index 0000000..88dc69c
--- /dev/null
+++ b/TestFiles/DA250-ConditionalWithRichXPath.docx
Binary files differ
diff --git a/TestFiles/DA251-EnhancedTables.docx b/TestFiles/DA251-EnhancedTables.docx
new file mode 100644
index 0000000..c6849c8
--- /dev/null
+++ b/TestFiles/DA251-EnhancedTables.docx
Binary files differ
diff --git a/TestFiles/DA252-Table-With-Sum.docx b/TestFiles/DA252-Table-With-Sum.docx
new file mode 100644
index 0000000..5fab5fa
--- /dev/null
+++ b/TestFiles/DA252-Table-With-Sum.docx
Binary files differ
diff --git a/TestFiles/DA253-Table-With-Sum-Run-Level-CC.docx b/TestFiles/DA253-Table-With-Sum-Run-Level-CC.docx
new file mode 100644
index 0000000..1a645bc
--- /dev/null
+++ b/TestFiles/DA253-Table-With-Sum-Run-Level-CC.docx
Binary files differ
diff --git a/TestFiles/DA254-Table-With-XPath-Sum.docx b/TestFiles/DA254-Table-With-XPath-Sum.docx
new file mode 100644
index 0000000..36d7793
--- /dev/null
+++ b/TestFiles/DA254-Table-With-XPath-Sum.docx
Binary files differ
diff --git a/TestFiles/DA255-Table-With-XPath-Sum-Run-Level-CC.docx b/TestFiles/DA255-Table-With-XPath-Sum-Run-Level-CC.docx
new file mode 100644
index 0000000..6183fa1
--- /dev/null
+++ b/TestFiles/DA255-Table-With-XPath-Sum-Run-Level-CC.docx
Binary files differ
diff --git a/TestFiles/DA256-NoInvalidDocOnErrorInRun.docx b/TestFiles/DA256-NoInvalidDocOnErrorInRun.docx
new file mode 100644
index 0000000..d4c40be
--- /dev/null
+++ b/TestFiles/DA256-NoInvalidDocOnErrorInRun.docx
Binary files differ
diff --git a/TestFiles/DA257-OptionalRepeat.docx b/TestFiles/DA257-OptionalRepeat.docx
new file mode 100644
index 0000000..452aa4b
--- /dev/null
+++ b/TestFiles/DA257-OptionalRepeat.docx
Binary files differ
diff --git a/TestFiles/DA258-ContentAcceptsCharsAsXPathResult.docx b/TestFiles/DA258-ContentAcceptsCharsAsXPathResult.docx
new file mode 100644
index 0000000..0b05a71
--- /dev/null
+++ b/TestFiles/DA258-ContentAcceptsCharsAsXPathResult.docx
Binary files differ
diff --git a/TestFiles/DA259-MultiLineContents.docx b/TestFiles/DA259-MultiLineContents.docx
new file mode 100644
index 0000000..7e610ff
--- /dev/null
+++ b/TestFiles/DA259-MultiLineContents.docx
Binary files differ
diff --git a/TestFiles/DA260-RunLevelRepeat.docx b/TestFiles/DA260-RunLevelRepeat.docx
new file mode 100644
index 0000000..56ffe48
--- /dev/null
+++ b/TestFiles/DA260-RunLevelRepeat.docx
Binary files differ
diff --git a/TestFiles/DA261-RunLevelConditional.docx b/TestFiles/DA261-RunLevelConditional.docx
new file mode 100644
index 0000000..92f2cdf
--- /dev/null
+++ b/TestFiles/DA261-RunLevelConditional.docx
Binary files differ
diff --git a/TestFiles/DA262-ConditionalNotMatch.docx b/TestFiles/DA262-ConditionalNotMatch.docx
new file mode 100644
index 0000000..646d2be
--- /dev/null
+++ b/TestFiles/DA262-ConditionalNotMatch.docx
Binary files differ
diff --git a/TestFiles/DA263-ConditionalNotMatch.docx b/TestFiles/DA263-ConditionalNotMatch.docx
new file mode 100644
index 0000000..646d2be
--- /dev/null
+++ b/TestFiles/DA263-ConditionalNotMatch.docx
Binary files differ
diff --git a/TestFiles/DA264-InvalidRunLevelRepeat.docx b/TestFiles/DA264-InvalidRunLevelRepeat.docx
new file mode 100644
index 0000000..3f69894
--- /dev/null
+++ b/TestFiles/DA264-InvalidRunLevelRepeat.docx
Binary files differ
diff --git a/TestFiles/DA265-RunLevelRepeatWithWhiteSpaceBefore.docx b/TestFiles/DA265-RunLevelRepeatWithWhiteSpaceBefore.docx
new file mode 100644
index 0000000..99c00f1
--- /dev/null
+++ b/TestFiles/DA265-RunLevelRepeatWithWhiteSpaceBefore.docx
Binary files differ
diff --git a/TestFiles/DA266-RunLevelRepeat-NoData.docx b/TestFiles/DA266-RunLevelRepeat-NoData.docx
new file mode 100644
index 0000000..dfe43f4
--- /dev/null
+++ b/TestFiles/DA266-RunLevelRepeat-NoData.docx
Binary files differ
diff --git "a/TestFiles/DB/HeadersFooters/Dest/Fax \050content control\051.docx" "b/TestFiles/DB/HeadersFooters/Dest/Fax \050content control\051.docx"
new file mode 100644
index 0000000..5213868
--- /dev/null
+++ "b/TestFiles/DB/HeadersFooters/Dest/Fax \050content control\051.docx"
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Dest/Fax.docx b/TestFiles/DB/HeadersFooters/Dest/Fax.docx
new file mode 100644
index 0000000..50274f6
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Dest/Fax.docx
Binary files differ
diff --git "a/TestFiles/DB/HeadersFooters/Dest/Letter \050content control\051.docx" "b/TestFiles/DB/HeadersFooters/Dest/Letter \050content control\051.docx"
new file mode 100644
index 0000000..0a111d0
--- /dev/null
+++ "b/TestFiles/DB/HeadersFooters/Dest/Letter \050content control\051.docx"
Binary files differ
diff --git "a/TestFiles/DB/HeadersFooters/Dest/Letter \050run-level insert element\051.docx" "b/TestFiles/DB/HeadersFooters/Dest/Letter \050run-level insert element\051.docx"
new file mode 100644
index 0000000..ac1ce75
--- /dev/null
+++ "b/TestFiles/DB/HeadersFooters/Dest/Letter \050run-level insert element\051.docx"
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Dest/Letter.docx b/TestFiles/DB/HeadersFooters/Dest/Letter.docx
new file mode 100644
index 0000000..b7916b8
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Dest/Letter.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Content-Controls.docx b/TestFiles/DB/HeadersFooters/Src/Content-Controls.docx
new file mode 100644
index 0000000..39815d1
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Content-Controls.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Disclaimer.docx b/TestFiles/DB/HeadersFooters/Src/Disclaimer.docx
new file mode 100644
index 0000000..09ab361
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Disclaimer.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Footer.docx b/TestFiles/DB/HeadersFooters/Src/Footer.docx
new file mode 100644
index 0000000..bb41e11
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Footer.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Letterhead-with-Watermark.docx b/TestFiles/DB/HeadersFooters/Src/Letterhead-with-Watermark.docx
new file mode 100644
index 0000000..14db3dc
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Letterhead-with-Watermark.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Letterhead.docx b/TestFiles/DB/HeadersFooters/Src/Letterhead.docx
new file mode 100644
index 0000000..e3a2c4e
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Letterhead.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Logo.docx b/TestFiles/DB/HeadersFooters/Src/Logo.docx
new file mode 100644
index 0000000..7a70481
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Logo.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Watermark-1.docx b/TestFiles/DB/HeadersFooters/Src/Watermark-1.docx
new file mode 100644
index 0000000..9c525ca
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Watermark-1.docx
Binary files differ
diff --git a/TestFiles/DB/HeadersFooters/Src/Watermark-2.docx b/TestFiles/DB/HeadersFooters/Src/Watermark-2.docx
new file mode 100644
index 0000000..30a90a0
--- /dev/null
+++ b/TestFiles/DB/HeadersFooters/Src/Watermark-2.docx
Binary files differ
diff --git a/TestFiles/DB001-Sections.docx b/TestFiles/DB001-Sections.docx
new file mode 100644
index 0000000..46b4acc
--- /dev/null
+++ b/TestFiles/DB001-Sections.docx
Binary files differ
diff --git a/TestFiles/DB002-Landscape-Section.docx b/TestFiles/DB002-Landscape-Section.docx
new file mode 100644
index 0000000..1416cca
--- /dev/null
+++ b/TestFiles/DB002-Landscape-Section.docx
Binary files differ
diff --git a/TestFiles/DB002-Sections-With-Headers.docx b/TestFiles/DB002-Sections-With-Headers.docx
new file mode 100644
index 0000000..7459636
--- /dev/null
+++ b/TestFiles/DB002-Sections-With-Headers.docx
Binary files differ
diff --git a/TestFiles/DB003-Only-Default-Header.docx b/TestFiles/DB003-Only-Default-Header.docx
new file mode 100644
index 0000000..854ba08
--- /dev/null
+++ b/TestFiles/DB003-Only-Default-Header.docx
Binary files differ
diff --git a/TestFiles/DB004-No-Headers.docx b/TestFiles/DB004-No-Headers.docx
new file mode 100644
index 0000000..dedbeda
--- /dev/null
+++ b/TestFiles/DB004-No-Headers.docx
Binary files differ
diff --git a/TestFiles/DB005-Headers-With-Images.docx b/TestFiles/DB005-Headers-With-Images.docx
new file mode 100644
index 0000000..e2c2138
--- /dev/null
+++ b/TestFiles/DB005-Headers-With-Images.docx
Binary files differ
diff --git a/TestFiles/DB006-Source1.docx b/TestFiles/DB006-Source1.docx
new file mode 100644
index 0000000..9e206b5
--- /dev/null
+++ b/TestFiles/DB006-Source1.docx
Binary files differ
diff --git a/TestFiles/DB006-Source2.docx b/TestFiles/DB006-Source2.docx
new file mode 100644
index 0000000..1176fe0
--- /dev/null
+++ b/TestFiles/DB006-Source2.docx
Binary files differ
diff --git a/TestFiles/DB006-Source3.docx b/TestFiles/DB006-Source3.docx
new file mode 100644
index 0000000..4011449
--- /dev/null
+++ b/TestFiles/DB006-Source3.docx
Binary files differ
diff --git a/TestFiles/DB007-Abstract.docx b/TestFiles/DB007-Abstract.docx
new file mode 100644
index 0000000..11dbf1a
--- /dev/null
+++ b/TestFiles/DB007-Abstract.docx
Binary files differ
diff --git a/TestFiles/DB007-AuthorBiography.docx b/TestFiles/DB007-AuthorBiography.docx
new file mode 100644
index 0000000..a2b4aa5
--- /dev/null
+++ b/TestFiles/DB007-AuthorBiography.docx
Binary files differ
diff --git a/TestFiles/DB007-Notes.docx b/TestFiles/DB007-Notes.docx
new file mode 100644
index 0000000..0b5f791
--- /dev/null
+++ b/TestFiles/DB007-Notes.docx
Binary files differ
diff --git a/TestFiles/DB007-Spec.docx b/TestFiles/DB007-Spec.docx
new file mode 100644
index 0000000..c040960
--- /dev/null
+++ b/TestFiles/DB007-Spec.docx
Binary files differ
diff --git a/TestFiles/DB007-WhitePaper.docx b/TestFiles/DB007-WhitePaper.docx
new file mode 100644
index 0000000..9adbaaa
--- /dev/null
+++ b/TestFiles/DB007-WhitePaper.docx
Binary files differ
diff --git a/TestFiles/DB010-FrontMatter.docx b/TestFiles/DB010-FrontMatter.docx
new file mode 100644
index 0000000..ad55bca
--- /dev/null
+++ b/TestFiles/DB010-FrontMatter.docx
Binary files differ
diff --git a/TestFiles/DB010-Insert-01.docx b/TestFiles/DB010-Insert-01.docx
new file mode 100644
index 0000000..dc94837
--- /dev/null
+++ b/TestFiles/DB010-Insert-01.docx
Binary files differ
diff --git a/TestFiles/DB010-Insert-02.docx b/TestFiles/DB010-Insert-02.docx
new file mode 100644
index 0000000..09646fb
--- /dev/null
+++ b/TestFiles/DB010-Insert-02.docx
Binary files differ
diff --git a/TestFiles/DB010-Template.docx b/TestFiles/DB010-Template.docx
new file mode 100644
index 0000000..1d5d02b
--- /dev/null
+++ b/TestFiles/DB010-Template.docx
Binary files differ
diff --git a/TestFiles/DB011-Body-With-Shape.docx b/TestFiles/DB011-Body-With-Shape.docx
new file mode 100644
index 0000000..a528370
--- /dev/null
+++ b/TestFiles/DB011-Body-With-Shape.docx
Binary files differ
diff --git a/TestFiles/DB011-Header-With-Shape.docx b/TestFiles/DB011-Header-With-Shape.docx
new file mode 100644
index 0000000..eb7aa63
--- /dev/null
+++ b/TestFiles/DB011-Header-With-Shape.docx
Binary files differ
diff --git a/TestFiles/DB012-Lists-With-Different-Numberings.docx b/TestFiles/DB012-Lists-With-Different-Numberings.docx
new file mode 100644
index 0000000..cec774a
--- /dev/null
+++ b/TestFiles/DB012-Lists-With-Different-Numberings.docx
Binary files differ
diff --git a/TestFiles/DB013a-Green-Heading1-Danish.docx b/TestFiles/DB013a-Green-Heading1-Danish.docx
new file mode 100644
index 0000000..380084c
--- /dev/null
+++ b/TestFiles/DB013a-Green-Heading1-Danish.docx
Binary files differ
diff --git a/TestFiles/DB013a-Red-Heading1-English.docx b/TestFiles/DB013a-Red-Heading1-English.docx
new file mode 100644
index 0000000..38526d4
--- /dev/null
+++ b/TestFiles/DB013a-Red-Heading1-English.docx
Binary files differ
diff --git a/TestFiles/DB013b-Blue-List-English.docx b/TestFiles/DB013b-Blue-List-English.docx
new file mode 100644
index 0000000..1044793
--- /dev/null
+++ b/TestFiles/DB013b-Blue-List-English.docx
Binary files differ
diff --git a/TestFiles/DB013b-Orange-List-Danish.docx b/TestFiles/DB013b-Orange-List-Danish.docx
new file mode 100644
index 0000000..53f8460
--- /dev/null
+++ b/TestFiles/DB013b-Orange-List-Danish.docx
Binary files differ
diff --git a/TestFiles/DB014-WebExtensions.docx b/TestFiles/DB014-WebExtensions.docx
new file mode 100644
index 0000000..0b5c762
--- /dev/null
+++ b/TestFiles/DB014-WebExtensions.docx
Binary files differ
diff --git a/TestFiles/E0010.html b/TestFiles/E0010.html
new file mode 100644
index 0000000..4c4da7b
--- /dev/null
+++ b/TestFiles/E0010.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+<style type="text/css">
+h1 font-family: Helvetica; }
+h1 em { color: red; }
+ol { font-size: smaller; }
+ul { font-size: larger; }
+.fzz { font-size: 1ex; }
+.fzz2 { font-size: 120%; }
+.fzz3 { font-size: .5in; }
+.fzz4 { font-size: 2.54cm; }
+.fzz5 { font-size: 254mm; }
+.fzz6 { font-size: 1pc; }
+.fzz7 { font-size: 100px; }
+.bar { text-indent: 2em; }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+ <ol>
+ <li>one</li>
+ <li>two</li>
+ </ol>
+ <ul>
+ <li>one</li>
+ <li>two</li>
+ </ul>
+ <p class='fzz'>bar1</p>
+ <p class='fzz2'>bar2</p>
+ <p class='fzz3'>bar3</p>
+ <p class='fzz4'>bar4</p>
+ <p class='fzz5'>bar5</p>
+ <p class='fzz6'>bar6</p>
+ <p class='fzz7'>bar7</p>
+ <p class='bar'>bar8</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/E0020.html b/TestFiles/E0020.html
new file mode 100644
index 0000000..59dc842
--- /dev/null
+++ b/TestFiles/E0020.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+<style type="text/css">
+h1 { font-family: Helvetica; }
+h1 em { color: red; }
+ol { font-size: smaller; }
+ul font-size: larger; }
+.fzz { font-size: 1ex; }
+.fzz2 { font-size: 120%; }
+.fzz3 { font-size: .5in; }
+.fzz4 { font-size: 2.54cm; }
+.fzz5 { font-size: 254mm; }
+.fzz6 { font-size: 1pc; }
+.fzz7 { font-size: 100px; }
+.bar { text-indent: 2em; }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+ <ol>
+ <li>one</li>
+ <li>two</li>
+ </ol>
+ <ul>
+ <li>one</li>
+ <li>two</li>
+ </ul>
+ <p class='fzz'>bar1</p>
+ <p class='fzz2'>bar2</p>
+ <p class='fzz3'>bar3</p>
+ <p class='fzz4'>bar4</p>
+ <p class='fzz5'>bar5</p>
+ <p class='fzz6'>bar6</p>
+ <p class='fzz7'>bar7</p>
+ <p class='bar'>bar8</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/HC001-5DayTourPlanTemplate.docx b/TestFiles/HC001-5DayTourPlanTemplate.docx
new file mode 100644
index 0000000..07254ee
--- /dev/null
+++ b/TestFiles/HC001-5DayTourPlanTemplate.docx
Binary files differ
diff --git a/TestFiles/HC002-Hebrew-01.docx b/TestFiles/HC002-Hebrew-01.docx
new file mode 100644
index 0000000..1eee33f
--- /dev/null
+++ b/TestFiles/HC002-Hebrew-01.docx
Binary files differ
diff --git a/TestFiles/HC003-Hebrew-02.docx b/TestFiles/HC003-Hebrew-02.docx
new file mode 100644
index 0000000..2b80d67
--- /dev/null
+++ b/TestFiles/HC003-Hebrew-02.docx
Binary files differ
diff --git a/TestFiles/HC004-ResumeTemplate.docx b/TestFiles/HC004-ResumeTemplate.docx
new file mode 100644
index 0000000..c1e50fe
--- /dev/null
+++ b/TestFiles/HC004-ResumeTemplate.docx
Binary files differ
diff --git a/TestFiles/HC005-TaskPlanTemplate.docx b/TestFiles/HC005-TaskPlanTemplate.docx
new file mode 100644
index 0000000..f5932f6
--- /dev/null
+++ b/TestFiles/HC005-TaskPlanTemplate.docx
Binary files differ
diff --git a/TestFiles/HC006-Test-01.docx b/TestFiles/HC006-Test-01.docx
new file mode 100644
index 0000000..a78f0c9
--- /dev/null
+++ b/TestFiles/HC006-Test-01.docx
Binary files differ
diff --git a/TestFiles/HC007-Test-02.docx b/TestFiles/HC007-Test-02.docx
new file mode 100644
index 0000000..f6628ca
--- /dev/null
+++ b/TestFiles/HC007-Test-02.docx
Binary files differ
diff --git a/TestFiles/HC008-Test-03.docx b/TestFiles/HC008-Test-03.docx
new file mode 100644
index 0000000..676c8cb
--- /dev/null
+++ b/TestFiles/HC008-Test-03.docx
Binary files differ
diff --git a/TestFiles/HC009-Test-04.docx b/TestFiles/HC009-Test-04.docx
new file mode 100644
index 0000000..797d694
--- /dev/null
+++ b/TestFiles/HC009-Test-04.docx
Binary files differ
diff --git a/TestFiles/HC010-Test-05.docx b/TestFiles/HC010-Test-05.docx
new file mode 100644
index 0000000..7df6284
--- /dev/null
+++ b/TestFiles/HC010-Test-05.docx
Binary files differ
diff --git a/TestFiles/HC011-Test-06.docx b/TestFiles/HC011-Test-06.docx
new file mode 100644
index 0000000..2d56602
--- /dev/null
+++ b/TestFiles/HC011-Test-06.docx
Binary files differ
diff --git a/TestFiles/HC012-Test-07.docx b/TestFiles/HC012-Test-07.docx
new file mode 100644
index 0000000..8971061
--- /dev/null
+++ b/TestFiles/HC012-Test-07.docx
Binary files differ
diff --git a/TestFiles/HC013-Test-08.docx b/TestFiles/HC013-Test-08.docx
new file mode 100644
index 0000000..0e21abe
--- /dev/null
+++ b/TestFiles/HC013-Test-08.docx
Binary files differ
diff --git a/TestFiles/HC014-RTL-Table-01.docx b/TestFiles/HC014-RTL-Table-01.docx
new file mode 100644
index 0000000..c700e95
--- /dev/null
+++ b/TestFiles/HC014-RTL-Table-01.docx
Binary files differ
diff --git a/TestFiles/HC015-Vertical-Spacing-atLeast.docx b/TestFiles/HC015-Vertical-Spacing-atLeast.docx
new file mode 100644
index 0000000..1b2fb60
--- /dev/null
+++ b/TestFiles/HC015-Vertical-Spacing-atLeast.docx
Binary files differ
diff --git a/TestFiles/HC016-Horizontal-Spacing-firstLine.docx b/TestFiles/HC016-Horizontal-Spacing-firstLine.docx
new file mode 100644
index 0000000..d6d3f12
--- /dev/null
+++ b/TestFiles/HC016-Horizontal-Spacing-firstLine.docx
Binary files differ
diff --git a/TestFiles/HC017-Vertical-Alignment-Cell-01.docx b/TestFiles/HC017-Vertical-Alignment-Cell-01.docx
new file mode 100644
index 0000000..008988c
--- /dev/null
+++ b/TestFiles/HC017-Vertical-Alignment-Cell-01.docx
Binary files differ
diff --git a/TestFiles/HC018-Vertical-Alignment-Para-01.docx b/TestFiles/HC018-Vertical-Alignment-Para-01.docx
new file mode 100644
index 0000000..755689a
--- /dev/null
+++ b/TestFiles/HC018-Vertical-Alignment-Para-01.docx
Binary files differ
diff --git a/TestFiles/HC019-Hidden-Run.docx b/TestFiles/HC019-Hidden-Run.docx
new file mode 100644
index 0000000..6603416
--- /dev/null
+++ b/TestFiles/HC019-Hidden-Run.docx
Binary files differ
diff --git a/TestFiles/HC020-Small-Caps.docx b/TestFiles/HC020-Small-Caps.docx
new file mode 100644
index 0000000..bc6e965
--- /dev/null
+++ b/TestFiles/HC020-Small-Caps.docx
Binary files differ
diff --git a/TestFiles/HC021-Symbols.docx b/TestFiles/HC021-Symbols.docx
new file mode 100644
index 0000000..a37dfa2
--- /dev/null
+++ b/TestFiles/HC021-Symbols.docx
Binary files differ
diff --git a/TestFiles/HC022-Table-Of-Contents.docx b/TestFiles/HC022-Table-Of-Contents.docx
new file mode 100644
index 0000000..c3590f5
--- /dev/null
+++ b/TestFiles/HC022-Table-Of-Contents.docx
Binary files differ
diff --git a/TestFiles/HC023-Hyperlink.docx b/TestFiles/HC023-Hyperlink.docx
new file mode 100644
index 0000000..ba2b244
--- /dev/null
+++ b/TestFiles/HC023-Hyperlink.docx
Binary files differ
diff --git a/TestFiles/HC024-Tabs-01.docx b/TestFiles/HC024-Tabs-01.docx
new file mode 100644
index 0000000..006d052
--- /dev/null
+++ b/TestFiles/HC024-Tabs-01.docx
Binary files differ
diff --git a/TestFiles/HC025-Tabs-02.docx b/TestFiles/HC025-Tabs-02.docx
new file mode 100644
index 0000000..e777276
--- /dev/null
+++ b/TestFiles/HC025-Tabs-02.docx
Binary files differ
diff --git a/TestFiles/HC026-Tabs-03.docx b/TestFiles/HC026-Tabs-03.docx
new file mode 100644
index 0000000..7e51f75
--- /dev/null
+++ b/TestFiles/HC026-Tabs-03.docx
Binary files differ
diff --git a/TestFiles/HC027-Tabs-04.docx b/TestFiles/HC027-Tabs-04.docx
new file mode 100644
index 0000000..7e87089
--- /dev/null
+++ b/TestFiles/HC027-Tabs-04.docx
Binary files differ
diff --git a/TestFiles/HC028-No-Break-Hyphen.docx b/TestFiles/HC028-No-Break-Hyphen.docx
new file mode 100644
index 0000000..7d89d07
--- /dev/null
+++ b/TestFiles/HC028-No-Break-Hyphen.docx
Binary files differ
diff --git a/TestFiles/HC029-Table-Merged-Cells.docx b/TestFiles/HC029-Table-Merged-Cells.docx
new file mode 100644
index 0000000..9382a7d
--- /dev/null
+++ b/TestFiles/HC029-Table-Merged-Cells.docx
Binary files differ
diff --git a/TestFiles/HC030-Content-Controls.docx b/TestFiles/HC030-Content-Controls.docx
new file mode 100644
index 0000000..236aa30
--- /dev/null
+++ b/TestFiles/HC030-Content-Controls.docx
Binary files differ
diff --git a/TestFiles/HC031-Complicated-Document.docx b/TestFiles/HC031-Complicated-Document.docx
new file mode 100644
index 0000000..54dba02
--- /dev/null
+++ b/TestFiles/HC031-Complicated-Document.docx
Binary files differ
diff --git a/TestFiles/HC032-Named-Color.docx b/TestFiles/HC032-Named-Color.docx
new file mode 100644
index 0000000..b3a4579
--- /dev/null
+++ b/TestFiles/HC032-Named-Color.docx
Binary files differ
diff --git a/TestFiles/HC033-Run-With-Border.docx b/TestFiles/HC033-Run-With-Border.docx
new file mode 100644
index 0000000..0c5c2ad
--- /dev/null
+++ b/TestFiles/HC033-Run-With-Border.docx
Binary files differ
diff --git a/TestFiles/HC034-Run-With-Position.docx b/TestFiles/HC034-Run-With-Position.docx
new file mode 100644
index 0000000..5cb6dd8
--- /dev/null
+++ b/TestFiles/HC034-Run-With-Position.docx
Binary files differ
diff --git a/TestFiles/HC035-Strike-Through.docx b/TestFiles/HC035-Strike-Through.docx
new file mode 100644
index 0000000..c87f48b
--- /dev/null
+++ b/TestFiles/HC035-Strike-Through.docx
Binary files differ
diff --git a/TestFiles/HC036-Super-Script.docx b/TestFiles/HC036-Super-Script.docx
new file mode 100644
index 0000000..3e3146d
--- /dev/null
+++ b/TestFiles/HC036-Super-Script.docx
Binary files differ
diff --git a/TestFiles/HC037-Sub-Script.docx b/TestFiles/HC037-Sub-Script.docx
new file mode 100644
index 0000000..1e79a89
--- /dev/null
+++ b/TestFiles/HC037-Sub-Script.docx
Binary files differ
diff --git a/TestFiles/HC038-Conflicting-Border-Weight.docx b/TestFiles/HC038-Conflicting-Border-Weight.docx
new file mode 100644
index 0000000..2556f15
--- /dev/null
+++ b/TestFiles/HC038-Conflicting-Border-Weight.docx
Binary files differ
diff --git a/TestFiles/HC039-Bold.docx b/TestFiles/HC039-Bold.docx
new file mode 100644
index 0000000..bd3e1cb
--- /dev/null
+++ b/TestFiles/HC039-Bold.docx
Binary files differ
diff --git a/TestFiles/HC040-Hyperlink-Fieldcode-01.docx b/TestFiles/HC040-Hyperlink-Fieldcode-01.docx
new file mode 100644
index 0000000..e21a15f
--- /dev/null
+++ b/TestFiles/HC040-Hyperlink-Fieldcode-01.docx
Binary files differ
diff --git a/TestFiles/HC041-Hyperlink-Fieldcode-02.docx b/TestFiles/HC041-Hyperlink-Fieldcode-02.docx
new file mode 100644
index 0000000..6147730
--- /dev/null
+++ b/TestFiles/HC041-Hyperlink-Fieldcode-02.docx
Binary files differ
diff --git a/TestFiles/HC042-Image-Png.docx b/TestFiles/HC042-Image-Png.docx
new file mode 100644
index 0000000..b6e52cc
--- /dev/null
+++ b/TestFiles/HC042-Image-Png.docx
Binary files differ
diff --git a/TestFiles/HC043-Chart.docx b/TestFiles/HC043-Chart.docx
new file mode 100644
index 0000000..a9221c7
--- /dev/null
+++ b/TestFiles/HC043-Chart.docx
Binary files differ
diff --git a/TestFiles/HC044-Embedded-Workbook.docx b/TestFiles/HC044-Embedded-Workbook.docx
new file mode 100644
index 0000000..3d5e9c4
--- /dev/null
+++ b/TestFiles/HC044-Embedded-Workbook.docx
Binary files differ
diff --git a/TestFiles/HC045-Italic.docx b/TestFiles/HC045-Italic.docx
new file mode 100644
index 0000000..054be4a
--- /dev/null
+++ b/TestFiles/HC045-Italic.docx
Binary files differ
diff --git a/TestFiles/HC046-BoldAndItalic.docx b/TestFiles/HC046-BoldAndItalic.docx
new file mode 100644
index 0000000..32d016f
--- /dev/null
+++ b/TestFiles/HC046-BoldAndItalic.docx
Binary files differ
diff --git a/TestFiles/HC047-No-Section.docx b/TestFiles/HC047-No-Section.docx
new file mode 100644
index 0000000..289edb5
--- /dev/null
+++ b/TestFiles/HC047-No-Section.docx
Binary files differ
diff --git a/TestFiles/HC048-Excerpt.docx b/TestFiles/HC048-Excerpt.docx
new file mode 100644
index 0000000..5d66622
--- /dev/null
+++ b/TestFiles/HC048-Excerpt.docx
Binary files differ
diff --git a/TestFiles/HC049-Borders.docx b/TestFiles/HC049-Borders.docx
new file mode 100644
index 0000000..5f03eb4
--- /dev/null
+++ b/TestFiles/HC049-Borders.docx
Binary files differ
diff --git a/TestFiles/HC050-Shaded-Text-01.docx b/TestFiles/HC050-Shaded-Text-01.docx
new file mode 100644
index 0000000..8104c06
--- /dev/null
+++ b/TestFiles/HC050-Shaded-Text-01.docx
Binary files differ
diff --git a/TestFiles/HC051-Shaded-Text-02.docx b/TestFiles/HC051-Shaded-Text-02.docx
new file mode 100644
index 0000000..c771ac8
--- /dev/null
+++ b/TestFiles/HC051-Shaded-Text-02.docx
Binary files differ
diff --git a/TestFiles/HC052-SmartArt.docx b/TestFiles/HC052-SmartArt.docx
new file mode 100644
index 0000000..09599dd
--- /dev/null
+++ b/TestFiles/HC052-SmartArt.docx
Binary files differ
diff --git a/TestFiles/HC060-Image-with-Hyperlink.docx b/TestFiles/HC060-Image-with-Hyperlink.docx
new file mode 100644
index 0000000..af086a0
--- /dev/null
+++ b/TestFiles/HC060-Image-with-Hyperlink.docx
Binary files differ
diff --git a/TestFiles/HC061-Hyperlink-in-Field.docx b/TestFiles/HC061-Hyperlink-in-Field.docx
new file mode 100644
index 0000000..05126fd
--- /dev/null
+++ b/TestFiles/HC061-Hyperlink-in-Field.docx
Binary files differ
diff --git a/TestFiles/HW002-Table01.docx b/TestFiles/HW002-Table01.docx
new file mode 100644
index 0000000..2a699b2
--- /dev/null
+++ b/TestFiles/HW002-Table01.docx
Binary files differ
diff --git a/TestFiles/HW002-Table02.docx b/TestFiles/HW002-Table02.docx
new file mode 100644
index 0000000..d25ba45
--- /dev/null
+++ b/TestFiles/HW002-Table02.docx
Binary files differ
diff --git a/TestFiles/HW002-Table03.docx b/TestFiles/HW002-Table03.docx
new file mode 100644
index 0000000..2204de5
--- /dev/null
+++ b/TestFiles/HW002-Table03.docx
Binary files differ
diff --git a/TestFiles/HW002-Table04.docx b/TestFiles/HW002-Table04.docx
new file mode 100644
index 0000000..ebd8db5
--- /dev/null
+++ b/TestFiles/HW002-Table04.docx
Binary files differ
diff --git a/TestFiles/HW002-Table05.docx b/TestFiles/HW002-Table05.docx
new file mode 100644
index 0000000..19189c3
--- /dev/null
+++ b/TestFiles/HW002-Table05.docx
Binary files differ
diff --git a/TestFiles/HW002-Table06.docx b/TestFiles/HW002-Table06.docx
new file mode 100644
index 0000000..e508abd
--- /dev/null
+++ b/TestFiles/HW002-Table06.docx
Binary files differ
diff --git a/TestFiles/HW002-Table07.docx b/TestFiles/HW002-Table07.docx
new file mode 100644
index 0000000..6565f31
--- /dev/null
+++ b/TestFiles/HW002-Table07.docx
Binary files differ
diff --git a/TestFiles/HW002-Table08.docx b/TestFiles/HW002-Table08.docx
new file mode 100644
index 0000000..a0cb07c
--- /dev/null
+++ b/TestFiles/HW002-Table08.docx
Binary files differ
diff --git a/TestFiles/HW002-Table09.docx b/TestFiles/HW002-Table09.docx
new file mode 100644
index 0000000..9232d0e
--- /dev/null
+++ b/TestFiles/HW002-Table09.docx
Binary files differ
diff --git a/TestFiles/HW002-Table10.docx b/TestFiles/HW002-Table10.docx
new file mode 100644
index 0000000..485f205
--- /dev/null
+++ b/TestFiles/HW002-Table10.docx
Binary files differ
diff --git a/TestFiles/HW002-Table11.docx b/TestFiles/HW002-Table11.docx
new file mode 100644
index 0000000..1fa1340
--- /dev/null
+++ b/TestFiles/HW002-Table11.docx
Binary files differ
diff --git a/TestFiles/HW002-Table12.docx b/TestFiles/HW002-Table12.docx
new file mode 100644
index 0000000..ed89a78
--- /dev/null
+++ b/TestFiles/HW002-Table12.docx
Binary files differ
diff --git a/TestFiles/HW002-Table13.docx b/TestFiles/HW002-Table13.docx
new file mode 100644
index 0000000..db2c994
--- /dev/null
+++ b/TestFiles/HW002-Table13.docx
Binary files differ
diff --git a/TestFiles/HW002-Table14.docx b/TestFiles/HW002-Table14.docx
new file mode 100644
index 0000000..65638a1
--- /dev/null
+++ b/TestFiles/HW002-Table14.docx
Binary files differ
diff --git a/TestFiles/HW002-Table15.docx b/TestFiles/HW002-Table15.docx
new file mode 100644
index 0000000..2b8b0af
--- /dev/null
+++ b/TestFiles/HW002-Table15.docx
Binary files differ
diff --git a/TestFiles/HW002-Table16.docx b/TestFiles/HW002-Table16.docx
new file mode 100644
index 0000000..681b8cf
--- /dev/null
+++ b/TestFiles/HW002-Table16.docx
Binary files differ
diff --git a/TestFiles/HW002-Table17.docx b/TestFiles/HW002-Table17.docx
new file mode 100644
index 0000000..22d88da
--- /dev/null
+++ b/TestFiles/HW002-Table17.docx
Binary files differ
diff --git a/TestFiles/HW002-Table18.docx b/TestFiles/HW002-Table18.docx
new file mode 100644
index 0000000..408348f
--- /dev/null
+++ b/TestFiles/HW002-Table18.docx
Binary files differ
diff --git a/TestFiles/HW010-SpanWithSingleSpace.docx b/TestFiles/HW010-SpanWithSingleSpace.docx
new file mode 100644
index 0000000..6d42f34
--- /dev/null
+++ b/TestFiles/HW010-SpanWithSingleSpace.docx
Binary files differ
diff --git a/TestFiles/HW010-Symbols01.docx b/TestFiles/HW010-Symbols01.docx
new file mode 100644
index 0000000..46ed41a
--- /dev/null
+++ b/TestFiles/HW010-Symbols01.docx
Binary files differ
diff --git a/TestFiles/HW010-Symbols02.docx b/TestFiles/HW010-Symbols02.docx
new file mode 100644
index 0000000..1b9f269
--- /dev/null
+++ b/TestFiles/HW010-Symbols02.docx
Binary files differ
diff --git a/TestFiles/HW010-Tab01.docx b/TestFiles/HW010-Tab01.docx
new file mode 100644
index 0000000..1cc09cd
--- /dev/null
+++ b/TestFiles/HW010-Tab01.docx
Binary files differ
diff --git a/TestFiles/HW010-TableWithEmptyRows.docx b/TestFiles/HW010-TableWithEmptyRows.docx
new file mode 100644
index 0000000..f6628ca
--- /dev/null
+++ b/TestFiles/HW010-TableWithEmptyRows.docx
Binary files differ
diff --git a/TestFiles/HW010-TableWithImage.docx b/TestFiles/HW010-TableWithImage.docx
new file mode 100644
index 0000000..676c8cb
--- /dev/null
+++ b/TestFiles/HW010-TableWithImage.docx
Binary files differ
diff --git a/TestFiles/HW010-TableWithThreeEmptyRows.docx b/TestFiles/HW010-TableWithThreeEmptyRows.docx
new file mode 100644
index 0000000..dbc644b
--- /dev/null
+++ b/TestFiles/HW010-TableWithThreeEmptyRows.docx
Binary files differ
diff --git a/TestFiles/LIR001-en-US-ordinal.docx b/TestFiles/LIR001-en-US-ordinal.docx
new file mode 100644
index 0000000..3f3114d
--- /dev/null
+++ b/TestFiles/LIR001-en-US-ordinal.docx
Binary files differ
diff --git a/TestFiles/LIR002-en-US-ordinalText.docx b/TestFiles/LIR002-en-US-ordinalText.docx
new file mode 100644
index 0000000..079dc6b
--- /dev/null
+++ b/TestFiles/LIR002-en-US-ordinalText.docx
Binary files differ
diff --git a/TestFiles/LIR003-en-US-upperLetter.docx b/TestFiles/LIR003-en-US-upperLetter.docx
new file mode 100644
index 0000000..e972f95
--- /dev/null
+++ b/TestFiles/LIR003-en-US-upperLetter.docx
Binary files differ
diff --git a/TestFiles/LIR004-en-US-upperRoman.docx b/TestFiles/LIR004-en-US-upperRoman.docx
new file mode 100644
index 0000000..ad7a59e
--- /dev/null
+++ b/TestFiles/LIR004-en-US-upperRoman.docx
Binary files differ
diff --git a/TestFiles/LIR005-fr-FR-cardinalText.docx b/TestFiles/LIR005-fr-FR-cardinalText.docx
new file mode 100644
index 0000000..8a5b154
--- /dev/null
+++ b/TestFiles/LIR005-fr-FR-cardinalText.docx
Binary files differ
diff --git a/TestFiles/LIR006-fr-FR-ordinal.docx b/TestFiles/LIR006-fr-FR-ordinal.docx
new file mode 100644
index 0000000..53304a7
--- /dev/null
+++ b/TestFiles/LIR006-fr-FR-ordinal.docx
Binary files differ
diff --git a/TestFiles/LIR006-fr-FR-ordinalText.docx b/TestFiles/LIR006-fr-FR-ordinalText.docx
new file mode 100644
index 0000000..179fe9d
--- /dev/null
+++ b/TestFiles/LIR006-fr-FR-ordinalText.docx
Binary files differ
diff --git a/TestFiles/LIR007-ru-RU-ordinalText.docx b/TestFiles/LIR007-ru-RU-ordinalText.docx
new file mode 100644
index 0000000..4f2b595
--- /dev/null
+++ b/TestFiles/LIR007-ru-RU-ordinalText.docx
Binary files differ
diff --git a/TestFiles/LIR008-zh-CH-chineseCountingThousand.docx b/TestFiles/LIR008-zh-CH-chineseCountingThousand.docx
new file mode 100644
index 0000000..ba5a7b8
--- /dev/null
+++ b/TestFiles/LIR008-zh-CH-chineseCountingThousand.docx
Binary files differ
diff --git a/TestFiles/LIR009-zh-CN-chineseCounting.docx b/TestFiles/LIR009-zh-CN-chineseCounting.docx
new file mode 100644
index 0000000..19c52a1
--- /dev/null
+++ b/TestFiles/LIR009-zh-CN-chineseCounting.docx
Binary files differ
diff --git a/TestFiles/LIR010-zh-CN-ideographTraditional.docx b/TestFiles/LIR010-zh-CN-ideographTraditional.docx
new file mode 100644
index 0000000..54ec18f
--- /dev/null
+++ b/TestFiles/LIR010-zh-CN-ideographTraditional.docx
Binary files differ
diff --git a/TestFiles/LIR011-en-US-00001.docx b/TestFiles/LIR011-en-US-00001.docx
new file mode 100644
index 0000000..5287d39
--- /dev/null
+++ b/TestFiles/LIR011-en-US-00001.docx
Binary files differ
diff --git a/TestFiles/LIR012-en-US-0001.docx b/TestFiles/LIR012-en-US-0001.docx
new file mode 100644
index 0000000..7c17a6a
--- /dev/null
+++ b/TestFiles/LIR012-en-US-0001.docx
Binary files differ
diff --git a/TestFiles/LIR013-en-US-001.docx b/TestFiles/LIR013-en-US-001.docx
new file mode 100644
index 0000000..8b242e2
--- /dev/null
+++ b/TestFiles/LIR013-en-US-001.docx
Binary files differ
diff --git a/TestFiles/LIR014-en-US-01.docx b/TestFiles/LIR014-en-US-01.docx
new file mode 100644
index 0000000..0943968
--- /dev/null
+++ b/TestFiles/LIR014-en-US-01.docx
Binary files differ
diff --git a/TestFiles/LIR015-en-US-cardinalText.docx b/TestFiles/LIR015-en-US-cardinalText.docx
new file mode 100644
index 0000000..21e29d8
--- /dev/null
+++ b/TestFiles/LIR015-en-US-cardinalText.docx
Binary files differ
diff --git a/TestFiles/LIR016-en-US-decimal.docx b/TestFiles/LIR016-en-US-decimal.docx
new file mode 100644
index 0000000..71854be
--- /dev/null
+++ b/TestFiles/LIR016-en-US-decimal.docx
Binary files differ
diff --git a/TestFiles/LIR017-en-US-decimalEnclosedCircle.docx b/TestFiles/LIR017-en-US-decimalEnclosedCircle.docx
new file mode 100644
index 0000000..016b265
--- /dev/null
+++ b/TestFiles/LIR017-en-US-decimalEnclosedCircle.docx
Binary files differ
diff --git a/TestFiles/LIR018-en-US-decimalZero.docx b/TestFiles/LIR018-en-US-decimalZero.docx
new file mode 100644
index 0000000..6ee8e2a
--- /dev/null
+++ b/TestFiles/LIR018-en-US-decimalZero.docx
Binary files differ
diff --git a/TestFiles/LIR019-en-US-lowerLetter.docx b/TestFiles/LIR019-en-US-lowerLetter.docx
new file mode 100644
index 0000000..87fedf6
--- /dev/null
+++ b/TestFiles/LIR019-en-US-lowerLetter.docx
Binary files differ
diff --git a/TestFiles/LIR020-en-US-lowerRoman.docx b/TestFiles/LIR020-en-US-lowerRoman.docx
new file mode 100644
index 0000000..f38f17f
--- /dev/null
+++ b/TestFiles/LIR020-en-US-lowerRoman.docx
Binary files differ
diff --git a/TestFiles/PB001-Input1.pptx b/TestFiles/PB001-Input1.pptx
new file mode 100644
index 0000000..cc03fe9
--- /dev/null
+++ b/TestFiles/PB001-Input1.pptx
Binary files differ
diff --git a/TestFiles/PB001-Input2.pptx b/TestFiles/PB001-Input2.pptx
new file mode 100644
index 0000000..d750fc3
--- /dev/null
+++ b/TestFiles/PB001-Input2.pptx
Binary files differ
diff --git a/TestFiles/PB001-Input3.pptx b/TestFiles/PB001-Input3.pptx
new file mode 100644
index 0000000..bf61b47
--- /dev/null
+++ b/TestFiles/PB001-Input3.pptx
Binary files differ
diff --git a/TestFiles/PP006-Videos.pptx b/TestFiles/PP006-Videos.pptx
new file mode 100644
index 0000000..1a68e90
--- /dev/null
+++ b/TestFiles/PP006-Videos.pptx
Binary files differ
diff --git a/TestFiles/PU/PU001-Test001.mht b/TestFiles/PU/PU001-Test001.mht
new file mode 100644
index 0000000..da471e4
--- /dev/null
+++ b/TestFiles/PU/PU001-Test001.mht
@@ -0,0 +1,2518 @@
+MIME-Version: 1.0
+Content-Type: multipart/related; boundary="----=_NextPart_01D386B7.E05F8590"
+
+This document is a Single File Web Page, also known as a Web Archive file. If you are seeing this message, your browser or editor doesn't support Web Archive files. Please download a browser that supports Web Archive, such as Windows® Internet Explorer®.
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test.htm
+Content-Transfer-Encoding: quoted-printable
+Content-Type: text/html; charset="windows-1252"
+
+<html xmlns:v=3D"urn:schemas-microsoft-com:vml"
+xmlns:o=3D"urn:schemas-microsoft-com:office:office"
+xmlns:w=3D"urn:schemas-microsoft-com:office:word"
+xmlns:m=3D"http://schemas.microsoft.com/office/2004/12/omml"
+xmlns=3D"http://www.w3.org/TR/REC-html40">
+
+<head>
+<meta http-equiv=3DContent-Type content=3D"text/html; charset=3Dwindows-125=
+2">
+<meta name=3DProgId content=3DWord.Document>
+<meta name=3DGenerator content=3D"Microsoft Word 15">
+<meta name=3DOriginator content=3D"Microsoft Word 15">
+<link rel=3DFile-List href=3D"Test_files/filelist.xml">
+<link rel=3DEdit-Time-Data href=3D"Test_files/editdata.mso">
+<link rel=3DOLE-Object-Data href=3D"Test_files/oledata.mso">
+<!--[if !mso]>
+<style>
+v\:* {behavior:url(#default#VML);}
+o\:* {behavior:url(#default#VML);}
+w\:* {behavior:url(#default#VML);}
+.shape {behavior:url(#default#VML);}
+</style>
+<![endif]--><!--[if gte mso 9]><xml>
+ <o:DocumentProperties>
+ <o:Author>Eric White</o:Author>
+ <o:LastAuthor>Eric White</o:LastAuthor>
+ <o:Revision>2</o:Revision>
+ <o:TotalTime>4</o:TotalTime>
+ <o:Created>2018-01-06T14:30:00Z</o:Created>
+ <o:LastSaved>2018-01-06T14:30:00Z</o:LastSaved>
+ <o:Pages>1</o:Pages>
+ <o:Words>11</o:Words>
+ <o:Characters>67</o:Characters>
+ <o:Lines>1</o:Lines>
+ <o:Paragraphs>1</o:Paragraphs>
+ <o:CharactersWithSpaces>77</o:CharactersWithSpaces>
+ <o:Version>16.00</o:Version>
+ </o:DocumentProperties>
+ <o:OfficeDocumentSettings>
+ <o:AllowPNG/>
+ </o:OfficeDocumentSettings>
+</xml><![endif]-->
+<link rel=3DthemeData href=3D"Test_files/themedata.thmx">
+<link rel=3DcolorSchemeMapping href=3D"Test_files/colorschememapping.xml">
+<!--[if gte mso 9]><xml>
+ <w:WordDocument>
+ <w:SpellingState>Clean</w:SpellingState>
+ <w:GrammarState>Clean</w:GrammarState>
+ <w:TrackMoves>false</w:TrackMoves>
+ <w:TrackFormatting/>
+ <w:HyphenationZone>21</w:HyphenationZone>
+ <w:PunctuationKerning/>
+ <w:ValidateAgainstSchemas/>
+ <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid>
+ <w:IgnoreMixedContent>false</w:IgnoreMixedContent>
+ <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
+ <w:DoNotPromoteQF/>
+ <w:LidThemeOther>FR</w:LidThemeOther>
+ <w:LidThemeAsian>EN-US</w:LidThemeAsian>
+ <w:LidThemeComplexScript>AR-SA</w:LidThemeComplexScript>
+ <w:Compatibility>
+ <w:BreakWrappedTables/>
+ <w:SnapToGridInCell/>
+ <w:WrapTextWithPunct/>
+ <w:UseAsianBreakRules/>
+ <w:DontGrowAutofit/>
+ <w:SplitPgBreakAndParaMark/>
+ <w:EnableOpenTypeKerning/>
+ <w:DontFlipMirrorIndents/>
+ <w:OverrideTableStyleHps/>
+ </w:Compatibility>
+ <m:mathPr>
+ <m:mathFont m:val=3D"Cambria Math"/>
+ <m:brkBin m:val=3D"before"/>
+ <m:brkBinSub m:val=3D"--"/>
+ <m:smallFrac m:val=3D"off"/>
+ <m:dispDef/>
+ <m:lMargin m:val=3D"0"/>
+ <m:rMargin m:val=3D"0"/>
+ <m:defJc m:val=3D"centerGroup"/>
+ <m:wrapIndent m:val=3D"1440"/>
+ <m:intLim m:val=3D"subSup"/>
+ <m:naryLim m:val=3D"undOvr"/>
+ </m:mathPr></w:WordDocument>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <w:LatentStyles DefLockedState=3D"false" DefUnhideWhenUsed=3D"false"
+ DefSemiHidden=3D"false" DefQFormat=3D"false" DefPriority=3D"99"
+ LatentStyleCount=3D"375">
+ <w:LsdException Locked=3D"false" Priority=3D"0" QFormat=3D"true" Name=3D"=
+Normal"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" QFormat=3D"true" Name=3D"=
+heading 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 7"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 8"/>
+ <w:LsdException Locked=3D"false" Priority=3D"9" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"heading 9"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 6"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 7"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 8"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index 9"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 7"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 8"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"toc 9"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Normal Indent"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"footnote text"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"annotation text"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"header"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"footer"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"index heading"/>
+ <w:LsdException Locked=3D"false" Priority=3D"35" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"caption"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"table of figures"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"envelope address"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"envelope return"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"footnote reference"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"annotation reference"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"line number"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"page number"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"endnote reference"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"endnote text"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"table of authorities"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"macro"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"toa heading"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Bullet"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Number"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Bullet 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Bullet 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Bullet 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Bullet 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Number 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Number 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Number 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Number 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"10" QFormat=3D"true" Name=3D=
+"Title"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Closing"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Signature"/>
+ <w:LsdException Locked=3D"false" Priority=3D"1" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"Default Paragraph Font"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text Indent"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Continue"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Continue 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Continue 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Continue 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"List Continue 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Message Header"/>
+ <w:LsdException Locked=3D"false" Priority=3D"11" QFormat=3D"true" Name=3D=
+"Subtitle"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Salutation"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Date"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text First Indent"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text First Indent 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Note Heading"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text Indent 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Body Text Indent 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Block Text"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Hyperlink"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"FollowedHyperlink"/>
+ <w:LsdException Locked=3D"false" Priority=3D"22" QFormat=3D"true" Name=3D=
+"Strong"/>
+ <w:LsdException Locked=3D"false" Priority=3D"20" QFormat=3D"true" Name=3D=
+"Emphasis"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Document Map"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Plain Text"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"E-mail Signature"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Top of Form"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Bottom of Form"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Normal (Web)"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Acronym"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Address"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Cite"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Code"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Definition"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Keyboard"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Preformatted"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Sample"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Typewriter"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"HTML Variable"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Normal Table"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"annotation subject"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"No List"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Outline List 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Outline List 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Outline List 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Simple 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Simple 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Simple 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Classic 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Classic 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Classic 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Classic 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Colorful 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Colorful 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Colorful 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Columns 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Columns 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Columns 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Columns 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Columns 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 6"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 7"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Grid 8"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 4"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 5"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 6"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 7"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table List 8"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table 3D effects 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table 3D effects 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table 3D effects 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Contemporary"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Elegant"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Professional"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Subtle 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Subtle 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Web 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Web 2"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Web 3"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Balloon Text"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" Name=3D"Table Grid"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Table Theme"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" Name=3D"Placeholder =
+Text"/>
+ <w:LsdException Locked=3D"false" Priority=3D"1" QFormat=3D"true" Name=3D"=
+No Spacing"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List Accen=
+t 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid Accen=
+t 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+ Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+ Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1 Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" Name=3D"Revision"/>
+ <w:LsdException Locked=3D"false" Priority=3D"34" QFormat=3D"true"
+ Name=3D"List Paragraph"/>
+ <w:LsdException Locked=3D"false" Priority=3D"29" QFormat=3D"true" Name=3D=
+"Quote"/>
+ <w:LsdException Locked=3D"false" Priority=3D"30" QFormat=3D"true"
+ Name=3D"Intense Quote"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2 Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1 Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2 Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3 Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List Accent=
+ 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+ Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid Ac=
+cent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List Accen=
+t 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid Accen=
+t 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+ Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+ Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1 Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2 Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1 Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2 Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3 Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List Accent=
+ 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+ Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid Ac=
+cent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List Accen=
+t 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid Accen=
+t 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+ Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+ Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1 Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2 Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1 Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2 Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3 Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List Accent=
+ 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+ Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid Ac=
+cent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List Accen=
+t 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid Accen=
+t 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+ Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+ Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1 Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2 Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1 Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2 Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3 Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List Accent=
+ 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+ Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid Ac=
+cent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List Accen=
+t 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid Accen=
+t 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+ Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+ Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1 Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2 Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1 Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2 Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3 Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List Accent=
+ 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+ Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid Ac=
+cent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"60" Name=3D"Light Shading Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"61" Name=3D"Light List Accen=
+t 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"62" Name=3D"Light Grid Accen=
+t 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"63" Name=3D"Medium Shading 1=
+ Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"64" Name=3D"Medium Shading 2=
+ Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"65" Name=3D"Medium List 1 Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"66" Name=3D"Medium List 2 Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"67" Name=3D"Medium Grid 1 Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"68" Name=3D"Medium Grid 2 Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"69" Name=3D"Medium Grid 3 Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"70" Name=3D"Dark List Accent=
+ 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"71" Name=3D"Colorful Shading=
+ Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"72" Name=3D"Colorful List Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"73" Name=3D"Colorful Grid Ac=
+cent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"19" QFormat=3D"true"
+ Name=3D"Subtle Emphasis"/>
+ <w:LsdException Locked=3D"false" Priority=3D"21" QFormat=3D"true"
+ Name=3D"Intense Emphasis"/>
+ <w:LsdException Locked=3D"false" Priority=3D"31" QFormat=3D"true"
+ Name=3D"Subtle Reference"/>
+ <w:LsdException Locked=3D"false" Priority=3D"32" QFormat=3D"true"
+ Name=3D"Intense Reference"/>
+ <w:LsdException Locked=3D"false" Priority=3D"33" QFormat=3D"true" Name=3D=
+"Book Title"/>
+ <w:LsdException Locked=3D"false" Priority=3D"37" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" Name=3D"Bibliography"/>
+ <w:LsdException Locked=3D"false" Priority=3D"39" SemiHidden=3D"true"
+ UnhideWhenUsed=3D"true" QFormat=3D"true" Name=3D"TOC Heading"/>
+ <w:LsdException Locked=3D"false" Priority=3D"41" Name=3D"Plain Table 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"42" Name=3D"Plain Table 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"43" Name=3D"Plain Table 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"44" Name=3D"Plain Table 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"45" Name=3D"Plain Table 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"40" Name=3D"Grid Table Light=
+"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46" Name=3D"Grid Table 1 Lig=
+ht"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51" Name=3D"Grid Table 6 Col=
+orful"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52" Name=3D"Grid Table 7 Col=
+orful"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"Grid Table 1 Light Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2 Acc=
+ent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3 Acc=
+ent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4 Acc=
+ent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"Grid Table 6 Colorful Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"Grid Table 7 Colorful Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"Grid Table 1 Light Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2 Acc=
+ent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3 Acc=
+ent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4 Acc=
+ent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"Grid Table 6 Colorful Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"Grid Table 7 Colorful Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"Grid Table 1 Light Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2 Acc=
+ent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3 Acc=
+ent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4 Acc=
+ent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"Grid Table 6 Colorful Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"Grid Table 7 Colorful Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"Grid Table 1 Light Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2 Acc=
+ent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3 Acc=
+ent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4 Acc=
+ent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"Grid Table 6 Colorful Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"Grid Table 7 Colorful Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"Grid Table 1 Light Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2 Acc=
+ent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3 Acc=
+ent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4 Acc=
+ent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"Grid Table 6 Colorful Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"Grid Table 7 Colorful Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"Grid Table 1 Light Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"Grid Table 2 Acc=
+ent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"Grid Table 3 Acc=
+ent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"Grid Table 4 Acc=
+ent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"Grid Table 5 Dar=
+k Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"Grid Table 6 Colorful Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"Grid Table 7 Colorful Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46" Name=3D"List Table 1 Lig=
+ht"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51" Name=3D"List Table 6 Col=
+orful"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52" Name=3D"List Table 7 Col=
+orful"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"List Table 1 Light Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2 Acc=
+ent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3 Acc=
+ent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4 Acc=
+ent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"List Table 6 Colorful Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"List Table 7 Colorful Accent 1"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"List Table 1 Light Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2 Acc=
+ent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3 Acc=
+ent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4 Acc=
+ent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"List Table 6 Colorful Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"List Table 7 Colorful Accent 2"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"List Table 1 Light Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2 Acc=
+ent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3 Acc=
+ent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4 Acc=
+ent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"List Table 6 Colorful Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"List Table 7 Colorful Accent 3"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"List Table 1 Light Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2 Acc=
+ent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3 Acc=
+ent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4 Acc=
+ent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"List Table 6 Colorful Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"List Table 7 Colorful Accent 4"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"List Table 1 Light Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2 Acc=
+ent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3 Acc=
+ent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4 Acc=
+ent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"List Table 6 Colorful Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"List Table 7 Colorful Accent 5"/>
+ <w:LsdException Locked=3D"false" Priority=3D"46"
+ Name=3D"List Table 1 Light Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"47" Name=3D"List Table 2 Acc=
+ent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"48" Name=3D"List Table 3 Acc=
+ent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"49" Name=3D"List Table 4 Acc=
+ent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"50" Name=3D"List Table 5 Dar=
+k Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"51"
+ Name=3D"List Table 6 Colorful Accent 6"/>
+ <w:LsdException Locked=3D"false" Priority=3D"52"
+ Name=3D"List Table 7 Colorful Accent 6"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Mention"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Smart Hyperlink"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Hashtag"/>
+ <w:LsdException Locked=3D"false" SemiHidden=3D"true" UnhideWhenUsed=3D"tr=
+ue"
+ Name=3D"Unresolved Mention"/>
+ </w:LatentStyles>
+</xml><![endif]-->
+<style>
+<!--
+ /* Font Definitions */
+ @font-face
+ {font-family:"Cambria Math";
+ panose-1:2 4 5 3 5 4 6 3 2 4;
+ mso-font-charset:0;
+ mso-generic-font-family:roman;
+ mso-font-pitch:variable;
+ mso-font-signature:-536869121 1107305727 33554432 0 415 0;}
+@font-face
+ {font-family:Calibri;
+ panose-1:2 15 5 2 2 2 4 3 2 4;
+ mso-font-charset:0;
+ mso-generic-font-family:swiss;
+ mso-font-pitch:variable;
+ mso-font-signature:-536859905 -1073732485 9 0 511 0;}
+@font-face
+ {font-family:"Calibri Light";
+ panose-1:2 15 3 2 2 2 4 3 2 4;
+ mso-font-charset:0;
+ mso-generic-font-family:swiss;
+ mso-font-pitch:variable;
+ mso-font-signature:-536859905 -1073732485 9 0 511 0;}
+ /* Style Definitions */
+ p.MsoNormal, li.MsoNormal, div.MsoNormal
+ {mso-style-unhide:no;
+ mso-style-qformat:yes;
+ mso-style-parent:"";
+ margin-top:0in;
+ margin-right:0in;
+ margin-bottom:8.0pt;
+ margin-left:0in;
+ line-height:107%;
+ mso-pagination:widow-orphan;
+ font-size:11.0pt;
+ font-family:"Calibri",sans-serif;
+ mso-ascii-font-family:Calibri;
+ mso-ascii-theme-font:minor-latin;
+ mso-fareast-font-family:Calibri;
+ mso-fareast-theme-font:minor-latin;
+ mso-hansi-font-family:Calibri;
+ mso-hansi-theme-font:minor-latin;
+ mso-bidi-font-family:Arial;
+ mso-bidi-theme-font:minor-bidi;}
+h1
+ {mso-style-priority:9;
+ mso-style-unhide:no;
+ mso-style-qformat:yes;
+ mso-style-link:"Heading 1 Char";
+ mso-style-next:Normal;
+ margin-top:12.0pt;
+ margin-right:0in;
+ margin-bottom:0in;
+ margin-left:0in;
+ margin-bottom:.0001pt;
+ line-height:107%;
+ mso-pagination:widow-orphan lines-together;
+ page-break-after:avoid;
+ mso-outline-level:1;
+ font-size:16.0pt;
+ font-family:"Calibri Light",sans-serif;
+ mso-ascii-font-family:"Calibri Light";
+ mso-ascii-theme-font:major-latin;
+ mso-fareast-font-family:"Calibri Light";
+ mso-fareast-theme-font:major-fareast;
+ mso-hansi-font-family:"Calibri Light";
+ mso-hansi-theme-font:major-latin;
+ mso-bidi-font-family:"Times New Roman";
+ mso-bidi-theme-font:major-bidi;
+ color:#2F5496;
+ mso-themecolor:accent1;
+ mso-themeshade:191;
+ mso-font-kerning:0pt;
+ font-weight:normal;}
+span.Heading1Char
+ {mso-style-name:"Heading 1 Char";
+ mso-style-priority:9;
+ mso-style-unhide:no;
+ mso-style-locked:yes;
+ mso-style-link:"Heading 1";
+ mso-ansi-font-size:16.0pt;
+ mso-bidi-font-size:16.0pt;
+ font-family:"Calibri Light",sans-serif;
+ mso-ascii-font-family:"Calibri Light";
+ mso-ascii-theme-font:major-latin;
+ mso-fareast-font-family:"Calibri Light";
+ mso-fareast-theme-font:major-fareast;
+ mso-hansi-font-family:"Calibri Light";
+ mso-hansi-theme-font:major-latin;
+ mso-bidi-font-family:"Times New Roman";
+ mso-bidi-theme-font:major-bidi;
+ color:#2F5496;
+ mso-themecolor:accent1;
+ mso-themeshade:191;}
+.MsoChpDefault
+ {mso-style-type:export-only;
+ mso-default-props:yes;
+ font-family:"Calibri",sans-serif;
+ mso-ascii-font-family:Calibri;
+ mso-ascii-theme-font:minor-latin;
+ mso-fareast-font-family:Calibri;
+ mso-fareast-theme-font:minor-latin;
+ mso-hansi-font-family:Calibri;
+ mso-hansi-theme-font:minor-latin;
+ mso-bidi-font-family:Arial;
+ mso-bidi-theme-font:minor-bidi;}
+.MsoPapDefault
+ {mso-style-type:export-only;
+ margin-bottom:8.0pt;
+ line-height:107%;}
+@page WordSection1
+ {size:595.3pt 841.9pt;
+ margin:1.0in 1.0in 1.0in 1.0in;
+ mso-header-margin:.5in;
+ mso-footer-margin:.5in;
+ mso-paper-source:0;}
+div.WordSection1
+ {page:WordSection1;}
+-->
+</style>
+<!--[if gte mso 10]>
+<style>
+ /* Style Definitions */
+ table.MsoNormalTable
+ {mso-style-name:"Table Normal";
+ mso-tstyle-rowband-size:0;
+ mso-tstyle-colband-size:0;
+ mso-style-noshow:yes;
+ mso-style-priority:99;
+ mso-style-parent:"";
+ mso-padding-alt:0in 5.4pt 0in 5.4pt;
+ mso-para-margin-top:0in;
+ mso-para-margin-right:0in;
+ mso-para-margin-bottom:8.0pt;
+ mso-para-margin-left:0in;
+ line-height:107%;
+ mso-pagination:widow-orphan;
+ font-size:11.0pt;
+ font-family:"Calibri",sans-serif;
+ mso-ascii-font-family:Calibri;
+ mso-ascii-theme-font:minor-latin;
+ mso-hansi-font-family:Calibri;
+ mso-hansi-theme-font:minor-latin;
+ mso-bidi-font-family:Arial;
+ mso-bidi-theme-font:minor-bidi;}
+</style>
+<![endif]--><!--[if gte mso 9]><xml>
+ <o:shapedefaults v:ext=3D"edit" spidmax=3D"1026"/>
+</xml><![endif]--><!--[if gte mso 9]><xml>
+ <o:shapelayout v:ext=3D"edit">
+ <o:idmap v:ext=3D"edit" data=3D"1"/>
+ </o:shapelayout></xml><![endif]-->
+</head>
+
+<body lang=3DEN-US style=3D'tab-interval:.5in'>
+
+<div class=3DWordSection1>
+
+<h1>Heading</h1>
+
+<p class=3DMsoNormal>Formula:</p>
+
+<p class=3DMsoNormal><!--[if gte msEquation 12]><m:oMathPara><m:oMath><i
+ style=3D'mso-bidi-font-style:normal'><span style=3D'font-family:"Cambria =
+Math",serif'><m:r>A</m:r><m:r>=3D</m:r><m:r>π</m:r></span></i><m:sSup>=
+<m:sSupPr><span
+ style=3D'font-family:"Cambria Math",serif;mso-ascii-font-family:"Cambri=
+a Math";
+ mso-hansi-font-family:"Cambria Math"'><m:ctrlPr></m:ctrlPr></span></m:s=
+SupPr><m:e><i
+ style=3D'mso-bidi-font-style:normal'><span style=3D'font-family:"Cambri=
+a Math",serif'><m:r>r</m:r></span></i></m:e><m:sup><i
+ style=3D'mso-bidi-font-style:normal'><span style=3D'font-family:"Cambri=
+a Math",serif'><m:r>2</m:r></span></i></m:sup></m:sSup></m:oMath></m:oMathP=
+ara><![endif]--><![if !msEquation]><span
+style=3D'font-size:11.0pt;line-height:107%;font-family:"Calibri",sans-serif;
+mso-ascii-theme-font:minor-latin;mso-fareast-font-family:Calibri;mso-fareas=
+t-theme-font:
+minor-latin;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:"Times Ne=
+w Roman";
+mso-bidi-theme-font:minor-bidi;mso-ansi-language:EN-US;mso-fareast-language:
+EN-US;mso-bidi-language:AR-SA'><!--[if gte vml 1]><v:shapetype id=3D"_x0000=
+_t75"
+ coordsize=3D"21600,21600" o:spt=3D"75" o:preferrelative=3D"t" path=3D"m@4@=
+5l@4@11@9@11@9@5xe"
+ filled=3D"f" stroked=3D"f">
+ <v:stroke joinstyle=3D"miter"/>
+ <v:formulas>
+ <v:f eqn=3D"if lineDrawn pixelLineWidth 0"/>
+ <v:f eqn=3D"sum @0 1 0"/>
+ <v:f eqn=3D"sum 0 0 @1"/>
+ <v:f eqn=3D"prod @2 1 2"/>
+ <v:f eqn=3D"prod @3 21600 pixelWidth"/>
+ <v:f eqn=3D"prod @3 21600 pixelHeight"/>
+ <v:f eqn=3D"sum @0 0 1"/>
+ <v:f eqn=3D"prod @6 1 2"/>
+ <v:f eqn=3D"prod @7 21600 pixelWidth"/>
+ <v:f eqn=3D"sum @8 21600 0"/>
+ <v:f eqn=3D"prod @7 21600 pixelHeight"/>
+ <v:f eqn=3D"sum @10 21600 0"/>
+ </v:formulas>
+ <v:path o:extrusionok=3D"f" gradientshapeok=3D"t" o:connecttype=3D"rect"/>
+ <o:lock v:ext=3D"edit" aspectratio=3D"t"/>
+</v:shapetype><v:shape id=3D"_x0000_i1025" type=3D"#_x0000_t75" style=3D'wi=
+dth:39pt;
+ height:14pt'>
+ <v:imagedata src=3D"Test_files/image001.png" o:title=3D"" chromakey=3D"whi=
+te"/>
+</v:shape><![endif]--><![if !vml]><img width=3D52 height=3D19
+src=3D"Test_files/image002.png" v:shapes=3D"_x0000_i1025"><![endif]></span>=
+<![endif]><span
+style=3D'mso-fareast-font-family:Calibri;mso-fareast-theme-font:minor-farea=
+st'><o:p></o:p></span></p>
+
+<p class=3DMsoNormal><span style=3D'mso-fareast-font-family:Calibri;mso-far=
+east-theme-font:
+minor-fareast'>Smart Art:<o:p></o:p></span></p>
+
+<p class=3DMsoNormal><span style=3D'mso-no-proof:yes'><!--[if gte vml 1]><v=
+:shape
+ id=3D"Diagram_x0020_1" o:spid=3D"_x0000_i1026" type=3D"#_x0000_t75" style=
+=3D'width:77pt;
+ height:52pt;visibility:visible' o:gfxdata=3D"UEsDBBQABgAIAAAAIQBgJjcoXAEAA=
+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
+CgAKAJsCAABtHQAAAAA=3D
+">
+ <v:imagedata src=3D"Test_files/image003.png" o:title=3D"" cropleft=3D"-952=
+8f"
+ cropright=3D"-9968f"/>
+ <o:lock v:ext=3D"edit" aspectratio=3D"f"/>
+</v:shape><![endif]--><![if !vml]><img width=3D103 height=3D69
+src=3D"Test_files/image004.png" v:shapes=3D"Diagram_x0020_1"><![endif]></sp=
+an></p>
+
+<p class=3DMsoNormal>Chart:</p>
+
+<p class=3DMsoNormal><span style=3D'mso-no-proof:yes'><!--[if gte vml 1]><v=
+:shape
+ id=3D"Chart_x0020_2" o:spid=3D"_x0000_i1025" type=3D"#_x0000_t75" style=3D=
+'width:433pt;
+ height:253pt;visibility:visible' o:ole=3D"">
+ <v:imagedata src=3D"Test_files/image005.png" o:title=3D""/>
+ <o:lock v:ext=3D"edit" aspectratio=3D"f"/>
+</v:shape><![endif]--><![if !vml]><img width=3D577 height=3D337
+src=3D"Test_files/image006.png" v:shapes=3D"Chart_x0020_2"><![endif]></span=
+><!--[if gte mso 9]><xml>
+ <o:OLEObject Type=3D"Embed" ProgID=3D"Excel.Chart.8" ShapeID=3D"Chart_x002=
+0_2"
+ DrawAspect=3D"Content" ObjectID=3D"_1576725400">
+ <o:WordFieldCodes>\s</o:WordFieldCodes>
+ </o:OLEObject>
+</xml><![endif]--></p>
+
+</div>
+
+</body>
+
+</html>
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/themedata.thmx
+Content-Transfer-Encoding: base64
+Content-Type: application/vnd.ms-officetheme
+
+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
+AAAAGwEAACcAAAB0aGVtZS90aGVtZS9fcmVscy90aGVtZU1hbmFnZXIueG1sLnJlbHOEj00KwjAU
+hPeCdwhvb9O6EJEm3YjQrdQDhOQ1DTY/JFHs7Q2uLAguh2G+mWm7l53JE2My3jFoqhoIOumVcZrB
+bbjsjkBSFk6J2TtksGCCjm837RVnkUsoTSYkUiguMZhyDidKk5zQilT5gK44o49W5CKjpkHIu9BI
+93V9oPGbAXzFJL1iEHvVABmWUJr/s/04GolnLx8WXf5RQXPZhQUoosbM4CObqkwEylu6usTfAAAA
+//8DAFBLAQItABQABgAIAAAAIQDp3g+//wAAABwCAAATAAAAAAAAAAAAAAAAAAAAAABbQ29udGVu
+dF9UeXBlc10ueG1sUEsBAi0AFAAGAAgAAAAhAKXWp+fAAAAANgEAAAsAAAAAAAAAAAAAAAAAMAEA
+AF9yZWxzLy5yZWxzUEsBAi0AFAAGAAgAAAAhAGt5lhaDAAAAigAAABwAAAAAAAAAAAAAAAAAGQIA
+AHRoZW1lL3RoZW1lL3RoZW1lTWFuYWdlci54bWxQSwECLQAUAAYACAAAACEAtvRnmJMHAADJIAAA
+FgAAAAAAAAAAAAAAAADWAgAAdGhlbWUvdGhlbWUvdGhlbWUxLnhtbFBLAQItABQABgAIAAAAIQAN
+0ZCftgAAABsBAAAnAAAAAAAAAAAAAAAAAJ0KAAB0aGVtZS90aGVtZS9fcmVscy90aGVtZU1hbmFn
+ZXIueG1sLnJlbHNQSwUGAAAAAAUABQBdAQAAmAsAAAAA
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/colorschememapping.xml
+Content-Transfer-Encoding: quoted-printable
+Content-Type: text/xml
+
+<?xml version=3D"1.0" encoding=3D"UTF-8" standalone=3D"yes"?>
+<a:clrMap xmlns:a=3D"http://schemas.openxmlformats.org/drawingml/2006/main"=
+ bg1=3D"lt1" tx1=3D"dk1" bg2=3D"lt2" tx2=3D"dk2" accent1=3D"accent1" accent=
+2=3D"accent2" accent3=3D"accent3" accent4=3D"accent4" accent5=3D"accent5" a=
+ccent6=3D"accent6" hlink=3D"hlink" folHlink=3D"folHlink"/>
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/image001.png
+Content-Transfer-Encoding: base64
+Content-Type: image/png
+
+iVBORw0KGgoAAAANSUhEUgAAAE4AAAAcCAIAAABAjh62AAAAAXNSR0IArs4c6QAAAAlwSFlzAAAW
+JQAAFiUBSVIk8AAAAhxJREFUWEftVztSwzAQVThLoGA4gXOChIaKlk4uk4aOG9DYZdLRUtEgnQCf
+IJMi9l2EVj9LjuRPPGSw420Sz6zsffvefjRjjKHrsJvrgAkoJ6gD5rqgabyYKVvEtDBYeK2OyQjm
+yKIkB0x5Yj0wNkIBR8nHeg5UztdPHGt2yCWvYahFCjKI6bC0vNyyHwlUW3R/q/4G1JsnkXBQWhik
+xkHMVvwBVun7BmEs0Q7TuChXe0xsin18QTowEcQOk1UROiYuNnQKFfzATWq4euD/S9mLE5bCaujg
+KKm8HNSc4Ki+WETC+fBwGggck9Vl8VEpUA5CC7MKVWpX4JdQmxQsBlmzhV8DAfNq4R90ozyNGQMD
+qqwIwTiBU1bAJmY3HJ0HF6rWrqTaHscX0G2pJ/NxT/Wo0cCz0zUkuwMX6csGJa/LZo7+xCM/ZOjh
+Ts1E+rXjijIj0Xyw+P7MQLFv2+5hlrkJSLF7+rqmu9SQkXmw+/dQmmGVxqtdBZVeI2o5pLFerWt/
+F2m5dnvfVxz3yJAquTMUlwck2c+Pzj7UVmMypW6dKFou14JVtzGpDnHXKyLBKhRpFsrV/tjAR9uk
+1vkJGk1pCoZ9pEI5+yq4VQgAlW+BPrmoDmBuBq3ed55TIALE7572jbOXfBFSaz1MUHv2OXXaNFvP
+60PmlOqHVqcwlw0xSrX1ki9jM9iYrsNGeDUPETdBHaOkJ1YnVgedgV83nDucXuSodAAAAABJRU5E
+rkJggk==
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/image002.png
+Content-Transfer-Encoding: base64
+Content-Type: image/png
+
+iVBORw0KGgoAAAANSUhEUgAAAE4AAAAcCAMAAAD4MnnTAAAAAXNSR0IArs4c6QAAAHJQTFRFAAAA
+AAAAAAA6AABmADo6ADpmADqQAGa2OgAAOgA6OgBmOjoAOjqQOma2OpDbZgAAZjoAZpC2ZpDbZrbb
+Zrb/kDoAkDpmkNv/tmYAtpBmtrZmttv/tv//25A625Bm27Zm2////7Zm/9uQ/9u2//+2///bnPY3
+4QAAAAF0Uk5TAEDm2GYAAAAJcEhZcwAAFiUAABYlAUlSJPAAAAAZdEVYdFNvZnR3YXJlAE1pY3Jv
+c29mdCBPZmZpY2V/7TVxAAABLklEQVRIS+2U23aDIBBFoamRXixJExp7IVEC//+LPYMS0aWLWPso
+T+qa2TNz5ghj65mpgPsUnBfVzKzJ8PLhxM787d9wIFmZRzgj4rc/1LEy6s4pvl02exnn19luGU5n
+P91IVh7UIpzeRDSmc6d6H2aK16eZp9M9uMsLDydnZwFnXHecuqhhFGZeg/hlzpyiT+2pb2l4uIlw
+KSqf50XX705tv/YVUpHr44M1zDMqkBWTR4MUDAZIMZbgFDnmLhz1Urf2N2Jc7TDaMdmct6puxwjY
+QZaVnqN5hxvXDmqLI4Rqm5oYh/QY4KbaJD9Y2cRPWMGI5l/T6RsBu6zQYbPBgB2ULpshsafkHVBT
+CHDXj+9uI30cLPMIh3jrJC4sK6kyIjOyVKx1codrwKpAT4Ffbq4S9DFAFn4AAAAASUVORK5CYIJ=
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/image003.png
+Content-Transfer-Encoding: base64
+Content-Type: image/png
+
+iVBORw0KGgoAAAANSUhEUgAAAHwAAABsCAMAAAHoERaBAAAAAXNSR0IArs4c6QAAAARnQU1BAACx
+jwv8YQUAAABgUExURQAAAERyxEh1xUx4x1B7yFZ/ylmCy1qDy1+GzWOJzmmO0Hyd1n+e13+f14Wj
+2Zix3pu04KG44bfJ6L7O69Pe8dfh8+Xr9+ft9+jt+PDz+vH0+/X3/Pj5/fn7/fz9/v///65836QA
+AAABdFJOUwBA5thmAAAACXBIWXMAABcRAAAXEQHKJvM/AAAD8klEQVRYR+2YD5ubIAzGL9rNzXbr
+0HrOnZbv/y2XBPyLCKhre2t/zx0iaXwBgYBvJjBC6oKkgpozbUHMf0hb0GEtiDhF+oKPK2fmXOQY
+q0oKUtLV6xkT9C9b2nuRCL7qeykyVujsLb73H/pq2HW9WrT9rC7t78sz1AlXwOk//cGYiV21acDY
+nkAlILvoO8Lj+boiFoLqF4FIj1mJ9WgJ8zcJspdwbqDCTqq61zG1pzT4oFRjBfF4vgPjlYwIs8tS
+ZzpG9gLgIDHpWmfasYXYvp7J8y/UA8OSsPqZoH2RHZ6vc4TRPYb9+COOINe3yNR+xfFBfy371s8E
+7YuEPb8bth1DO84+CU2Jg78vm9ghu+LSW8tugs7YSzEYQWH1M0H7IjSBNuCcf4v8M/cYB2qkwqQd
+u3t1wKRIxBnSLMbVvFHFY+zuKbpEDbA7Xud51K7z4kHdYxqStg7vWFCPcNzglg2tuE4d5keQwz2+
+5HARCQ2b/KjLhzjcIb/QH0Rqg2bwr7rOD3bfwP0rr/NDqNtxrrpYdM/gxKt+foDiwIm2djjU0wyH
+nAqMg+jY85hd5wm53xc9/lZB7rohK/hf3XMpjTE+xe5OEbaYWx6HWN05qGKMlxReMdrOa1jdaXdA
+z0BjTbMt749LAxzqrFrrA/4M9raTS5Eod7zOY3eHikMDu2N+eNjsWXD34endN/DsXafzI3DIyv7M
+Y2NB3YynJsvuuEzgvkoccfLPLzwO9Sv8+cDFgrKBxwH2qQD3hfRxhJ40g8P99OuY/jzTjpI2liau
+rkMbmYvXcmGwh/sGdnDXNVnB47p7PNbmTgN+q7vA1Up8lwc8UfKxUtsGONQlCJxqJW3vOZnicK9x
+pYIU32/DiTb2+LjzShG2XAzdoT7ARSVTbO6ePL37BtD9U6ObcXO0uH4LN+Ul/mnE07rBXWpUSPfn
+oGVWiItW9CQzvLse8YRNtcE1nLbPATUKF4+rEld+IipQU1ANsB6omZMhKlqzm23imFP9kNYZ/qu5
+q3rBhzXdftWnAdXtnXhczW+V7awZcEJS2+OKK9GLY0LltIXwZI34brzEn1j8PjyCuO6Jm/IS/zzi
+IWFziS3iKqZwKFcRjj7cB0TUTeJ8iYp3TjHJOcwFRNZN3Z7WZziVXytMUbdVVXXwYds7z5tvxRlE
++aVA3XaLk3t3/DbxuFJ9/s56vJngjZ0nq8T34iX+xOL34RHEdU/clJf4o4vz0Z9pku4DwSYCW96G
+lDuL485FBTBx/U2bmLTGqMp16nMuNrWcT+XqW4HeSWAs73N0WWQvcW4u0+ec7CXe7yECdhN7ievJ
+wI3ucw4CxfflJf7E4veBxe/F29tfGRcoTaU1cqAAAAAASUVORK5CYIJ=
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/image004.png
+Content-Transfer-Encoding: base64
+Content-Type: image/png
+
+iVBORw0KGgoAAAANSUhEUgAAAJoAAABoCAYAAAAJpekFAAAAAXNSR0ICQMB9xQAAAAlwSFlzAAAW
+JQAAFiUBSVIk8AAAABl0RVh0U29mdHdhcmUATWljcm9zb2Z0IE9mZmljZX/tNXEAAA8vSURBVHja
+7Z35dxRVFsc9x//B8Rf9fc6ZmeM+IiCEgCOujKKCo7iwQ1gCgigqhk1ZRXYEhaAgKoIsArJkXztL
+p0kgIaGTdHaSdGfrrJ1w535vV8UmhAGdAF3kNud7Cl5VV1d3fXj3vvfqfd9dERERd6lUN1t9ejIi
+upd1v8ryuo91dzCDZmO1qywvN+ueYAYtm0WVHh+V1LRTmcoyKjW2xquV9ZdgBs2Oqxy7OI0GhsXS
+sPB4lUU0dFacbKvqOnELmywCmo0GTmfQZserLKKhM+NkaynQ/rMkjQbPiKPhcxJUFhEgw1ZBUylo
+CpqCpqCpFLRg0CBuHT86OZoenfS7Hp8aQ6HhCpqC1kc/7GMM2NS1WbTrhIv2ni6l71l7TpXSkt15
+0j3Tn2plBe1m/Kjh8TRibiKt3JtPFbWtlJ5XR4cTK+lwQgVFZVaTp6mDIhk+fI8hM/sHbAraTQmX
+cbR+v5M6uy7T6fRqenZBEj00MZoemhAl/X9fHSmmy/wH0L38cSqFzvbDiZA6YFoMPcnw/XNKTHfI
+7e27hrJwvBxj6Mlg/s+noPWths6Kp3dXZFClu41yXY006sNkgcvcb9Zgp9KqqcPXRcu/uyD7Ry1M
+oQ0HnLRqXwFNWGmXEHsmo5p+Ta6i6esc9AQfEzrHf7NC+DPwns0HC+lk2iU6xTAfjK+gKWvsAunw
+IMz/FLQ+FBL8wXyjdx53EVdm9OX+i1I2bPaVxwydGU/zt56jzs7LdCylUvI1fCeA2dLWSVkF9XQ8
+tYo2MnjnihupqLKZZq4/KxAN4/c//0Ey7T5RQvUcgn+MLqOdx1zkrPDSuaIGmv5FFoXwTQ22xoaC
+1segDeHaJiqzRsJm+Mazcp2hPY5D2VvL06musYOyCxs4n0ugMRE2SsvzUBe/72BcOT0zP4n+Ou6U
+gNPQ3CG123McggdOj6MVnPvVezto8y+FEj4fGH+GZnzpIHdDh4TjkfMTu2+sgnaHgobQmXC2lto6
+umgWaqFeQEPZuGXpVMXhNa+0iZ5mMABa1sV6qvK00rS1dgEINdMLHHrTuDHhLPcKnAOmxVI8n7+k
+uoWeX5AseR/yOHxu6vk6KR/9SaqC1h9AAwjt1wEN0JTXtEothvfhQQEHg1bKoExbmyXfD6AhTCbn
+eMhV1UzvfJ7BtVoy5bmaqLW9i0NlI9k5zCLUomZsae+k6vo2eu1Tm4J2x4dOzqOQwF/mHO2Dr3I4
+r7o6XwJoE1ZmUh3nWMjFBkyNlUefAFpZTYuESxM01Ggp5zxUzHna259l0OhFNqndahvaJQdc9E2u
+9MstjsyjD7ef41zOQSPnJWmOdqe3OpGwr/mhgJpbO2nzoUKpzUICahf/YzMJ0rr0dXZR5G8u6cq4
+EdBQo4Xwe886G6iYa7jnuLZ7cHyUhFkI5xkQpKMOClpfd2/wDzqOw6KzvJkKyrz0GudeaFUCGmgw
+//1VrpUulHipvLZFQii6Ll5fcmOgASQ0DNp8CM0O/3Gz4rtbmiO0H63/dNjipm/42UneFh/lc7KP
+fjVz3xtL0gVAN7c4IyJzJafDdwFoyLMq3a0U1gM0W24dlV5qkfM8wY2BsHUOqqhpo8IKDqdGLfcC
+127bjxTRvqhSgQ0dwApaPxiCQksSNRBAwzBUYYVXVMYNgPQLdVLrmXkdcrax/J0yuBwhMbAxANAS
+s910keHE+TDqgJv21rIMKUODAufF+2KzauhNbs2GBllDQEG7ybUarhFdDUjiURtBgOXfH6XIPvPH
+B2z/ei+RXuFj0WIcOS+xO89CH9vLXP4qlz9tlIcyyMgFA8+N8+LfQ2YGH2QK2i0QWqG41kChU/da
+LVYA1DOZ/1/l1zuvgqYPPvYrKWgqBU1BU9AUNJWCplLQFDQFzTqgjVqYTA+Mj5KxPZU1hEeZsK3w
+WAi01fvy6f1tOfTRjvMqiwhPlmBb5+2yDmj6svxLQdOXgtYN2mlj1o/MlVRZQocS/Perpe2ydUDD
+IzH/eOfMFXMaVcGthydGydZSjQHt3tDuDQVNpaCpFDQFTUFT0FQKWtAJ14UJJKYGiGJka5bBoEWN
++BS0PyXzUewdR4tl2pwpW66HUs57ZEa5WXYGdlWfpATdbHIFzSI1Gn7M6euyumeOL+VtYbmXfJ2X
+advhQvp0p39WOcb9nnk/SY4P7QXY3mq73srNsmGGgrmWVND6OnSGxcms8ceMWeMYxYBX2phPbfSI
+4V+L8In5nFBgrQbozPJAaExPj5CAySeY0mdOHDbV83wKWj9oDAwzZo3H2GuoytNG736e4YeFf/Bn
+uTbbebyYMjmcztuSbUASJwYuMOCLc9TSW8szZOYTZr6PjUiT2elRfC5Znmi6f+b71kNFdKGkSSYk
+Qynn3DR6Uap8joKmoPH+RDFowWt/TJlMIIYtwqRVdplgjNfyb/Ok9sP3fG9zNjV4fZLbYUIxznEq
+7ZJYXMExctJqOy3kcFxe20rnixvFoDmkl7CsoPUj0HDzAdb4FZlSnphdK8bKaJkid0NZS2unuDhi
+VvoT4nlbRM1tnZLfofW66WAhtfu6aPfJEgmrj0/xh+M5m7LFXOZIUmXQuT4qaLcYtOFGHvYi10xw
+Z3SJp0am5Fu7jrukhQpP2qRst8xoR60GGyz4o41fmSGgnUz3O3uj5jI9cWGPBS81x8UGclY000sf
+BZcZn4J2G0BDIo/aZu0PBeJzhrAHK9FjKVX0E4dSmCDnc+6F97z0carkYSdsVdJSxTlgJ1/CgCJ3
+M524MUv9xYUplJZbR9V1bWI1GqKg9W/QzI7dmV+elVD3HYdAeGfAGHndTxfFKRJOQRG7cmkK11o1
+9W209XCR3w+Xz4s+OThDjlHQFLTrgQZAAAqcuG15Hlr27QVyOP2u2nAEgm3oNm5VbjxQKEn/vC05
+3Uv7pOV6yNvqE7v33kInXLw1dCpoItQ2cBBCXgbrKXRpoKaCaxDcgo5yQh/vcIsJHxoMcBkSIxcG
+CwtiwPU78rfAxkAMt05zBMADceXaGOhvoCXluKVrYsKKjKv6t9CqDN+QTa3cooTnLVqL6DtDzbXq
++wJqavFJV8eWQ4V+m6twf/fI2AibNBY8jR302Z58mrzaLjOMEG5TuLZDXqdmyf0MNCw0lnzOTW8s
+TbsqZwJUqKmw+gkM+1Z+n0+Dpsey4iTHgmEfXCDD1mVdsfqK2Ue2L6pMzPhMoSEhnrnaYdv/HhMy
+h4auFcbM4aUhPYaP/AveG+W92ISa+wOHoEKucayC1g9AC72Bwe7QawyKX++9gQPqOqiuDz6qFDSV
+gqagKWgKmkpBUyloCpqCZinQ4L3xd3hvTIpWWUQPG+uKWsp743hKFf0QXUb7Y8tVVlGM/341W8lN
+SF/qj6ag6UtB05eC9odAU7NkNUu+JaCp/bvav2s/mko7bFUKmoKmoClo//ePbDwNG6xPvypoQQwa
+fjjMSLqe8NQr1kKHgQu2CpqCdsMCPM8tSJKJJaZGf5IqFgavLErtLnt1kY3hSpJJwZhSt/mgk56a
+m9ivDPgUtD8LGQszlZZE5oktAeSqapHZ4Y3NPqpyt8m/UY6JvDPWO2jLL4XU4euiyOMuBU1B+2M1
+2sj5SVKDQSM5JH53slQm9a79qYCexb6FKTSKhUnCWw8VivXBN78W8w+eIM4/mNOJ2eqhPc6LsiGG
+GR+OgQKn6SEcm+VQb15oyAUHBxxjzglV0KyYoxkJPoSOSNgXtLZ3UkRkLg2YGstAxIsAFkDztnTS
+V4eLJMROWpUpk35hqmdOxTNzudf5uwFQ2BvAL23KWrvAjBsFeBCa8V44CU1YmSmPTJl2WLgu83zw
+8sAxk9fY6Y2l6TRibsJt9eJQ0PpAmNy7/UixgLb02zwBwoQH8oPmo2PJlfRzXLlMFkZoLa5qFhAE
+Iv4+45anU05RA0Xba2jfmTK6xOE4v6xJhm4GhcWKe1BWQT2V1bSIw2NdUwcdTqgQeE0LBHitwZwP
++8zPgZbxdYWG3z6bBAXtFoC22cjRYHGwg8Mnah0YvDjLveSs8EqjAYZ7AM1+sV5GnTPy62jaF1k0
+aIY/PL7J+y64msTCCrXeQAbv8z35VM9AHYgtl9oQN3PtjwXk5TC964RLQjTcv2Oyaqi6vp0+2JZz
+2343Be0WgAbvDOzDUoJPcQgbZFw7rKjQgIB7N/w2xi1LJ4ezXkD5+OvzHJKj5QbBfA+28u7GDgpb
+55CV/R6eFC3l0QxRpbtVwEVemFPYQIk5teIgifHFv719mqauyRL/j1/iKzj/i7sttZqCdotCJxoD
+O44W0Yg5id151Iq9+RJSASLyPIAGD9qMgjoxPUbib85Cj8mskXPAZhQQLo70W8nnFDWSjxshsCqd
+uNJOTQwu1jIQq3luGSPsfv0r3IeIErJrjdnt8QranQwaWp3o3jBBg6kLQENoDQQN4fO1CJuAhtzr
+Zc7B0vLqZL2CC5x32fN/XxwD5bCYH8PHf7j9PF9DF3ka2ymre1GNBrHDgmXppoNO45oUNAWtB2i4
+QbAUhbU7AMIxD4w/02MJoFgJiXM35cjnwFsNi7Ci9Ru4TNDt/M0UtCAHbXi4//yYkNPe0SUrsyCf
+Mz8bnwWQnpyBxkQGNXDohCcb+uPMmxti5Hn4zdDNoaApaFeDNsffiYslfwBalL2aRs5PlAc/kewj
+N0Nj4qn3Euj5BUkUxblcB4fYRZyjYf+DfBw+C3ndrA1nu8+poFkQNISn3SdKpFtihZjpXQnazmPF
+sm/PqZIrQMMYKF5I1h+bHENvf5YhfV4F5V4au9gmHb7miAH6x+Di3dLWKUl9JH/e3tOlUnt9c8xF
+T89PEojQ9QGbUliMoi8u8oRL3L7xHvjgDg6LVdCsChpucPjGbLmpE1dlXtHbD83e4BCv2rmbsiV0
+DTM6V9Hrv5PLZ613yPfBCADgW81AmSuk/H6jEqTWW7wrV7pJsPbA0eRK2nDA6R+hMI5F7YoB/z0n
+S2Q/jkO3BhwksT6Vhk4Lg4abh+tBAg7oRvTYb+5DSA0sh7WolBu5E24G8i/oWl0QyLWweJkp06m7
++1qMnAznNY8J/AxtDOgTtvr0hoKmUtBUCpqCplLQVAqagqagBQVo6MxEfxEuXmUNDTXGbS0Gmv/B
+P/Q1qawhrBmPrVVAy8ZVVnp8VFLTTmUqy6jU2Bqv1mAHzcZqV1lebtY9wQzavaz7VZbXfay7gxY0
+lepa+i9euBCjYsOQXgAAAABJRU5ErkJggk==
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/image005.png
+Content-Transfer-Encoding: base64
+Content-Type: image/png
+
+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
+twnfu73f8G3ed643T4f7xlMbq+ghv7P3FNPUf4v7p8u/L6/3r11tf9TsTnrIb/Il2f037V7wC35f
++tm7676VZnZlDvvXnjuad8DvcDWxph5urrvH/jvcP5U5ip+b2Pz08nMwAz1MsvW7u/Rw033tf8jl
+9ufbm+2PStz+vP05j+e+96dtVT0s43J/2XX9j56UrsRr19U5LGrZ1/It3Zcebp4uHrqu63985uox
+Xtu8zCE/x3SyVtbD2/u4Q6Mr3LXxlm64h5XsMmpvXnu4uYgxjR4ysNufmnMPW3jpYdf/LHb/MxLR
+lXht+xMTO88Z99LDWnZ56yg/GE4P7/o3mXelSvRw+1rU3PmZjNO0ih5ayj1cP/dw/dzD9XMP1889
+XD/3cP3cw/VzD9fPPVw/93D93MP1Kz20lfsv7qHZ4soY8lbVzBbhMTRbnMfQbHEeQ7PFeQzNFucx
+NFucx9BscR5Ds8V5DM0W5zE0W5zH0GxxHsPT9Hw14jklpztQWfskj+EJef6nX3Vd95s/96/Om5fr
+cvtXXx+f/+7X/8jKlsfwNHkMT8Zt1/23P5ZJ+ZfflFEZOy933U9vf/QRD5cvf/vz6ar7+/7la5bH
+8DR5DE/F9c5AtRnDFx7DE+cxPBF33c7slHn57W/iQ8uL/u9tPn0pr/z6sry/7MepLP7P8nr37Z/7
+0F//+td/G7d58TaG26mTrJcxLC9LvVLCQ3kSPIYnYvedYT8nMX/lQ1WNX8ffWe2HiEHL3xu+TN37
+94YPl9vi1LOleQxPxO64vQ7Q68Q8/8v/+PWvy7u1r/2fPX5ZnDuG111XikW9jze34/MYnogyOy+j
+1Xs3huVD1t/+a/nXbasx7MvYyfAYnorb8rkes3H77f0YvnzwuJ2fnTHkw0sxNIZkEXjLsJPgMTwZ
+z7+PL8kUZdTejWH/JZq//lX5UPL9GJZXtzcQH8fwNesl8NR/+efX/hrNqfAYmi3OY2i2OI+h2eI8
+hmaL8xiaLc5jaLY4j6HZ4jyGZovzGJotzmNotjiPodniPIZmi/MYmi3OY2i2uKExvPUvo5kd08AY
+3vrvIpgd1ccxfL767/0vie7+Jmn4k5l9DrP0ztAYxt9buP34xxXM7CCqH5Re+0NTsyMZ+hLNQ/+n
+aP1VGrNjGRpDMzsqj6HZ4jyGZovzGJotzmNotjiPodniPIZmi/MYmi3OY2i2OI+h2eI8hmaL8xia
+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
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/image006.png
+Content-Transfer-Encoding: base64
+Content-Type: image/png
+
+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
+zEUI6yob/3/RN69/TwQ9cQAAAABJRU5ErkJggk==
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/oledata.mso
+Content-Transfer-Encoding: base64
+Content-Type: application/x-mso
+
+0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAA
+EAAA/v///wAAAAD+////AAAAAAAAAAD/////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////9
+/////v///wMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8A
+AAAQAAAAEQAAABIAAAATAAAA/v//////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////1IA
+bwBvAHQAIABFAG4AdAByAHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAWAAUA//////////8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBJ0O76htMB
+/v///wAAAAAAAAAAXwAxADUANwA2ADcAMgA1ADQAMAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAABgAAgH///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAACAAAAoCIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////wAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////
+////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHYA
+AHic7T0HXBRH97N3Byz9KGKX40AEqQcIWBDsJUhTwcSCBxyIngc5DjuKwRoNKhoLYpfYFVFT7KjR
+oJjYu8aaaGzRqMRovP/M7O7d3l7hyFf++b7P4Tdzu29fmXnz3tuZ2dnl1A9ON1dtb3ILcFJHwAfv
+1NbAkgUj6IyTEAAeff5OrVYzYPX79B+V/oRZQPdhW/oX9bkVzCTM1jDbwGwLsx3M9jA7wOxImQBw
+gtkZZheYXWFuALMbzA1hbgRzY5ibwNwU5mYwN4e5BczuMItg9oBZDLMnzF4wt4TZG+ZWMPvA7Atz
+a5j9YPaHOQDmQJiDYA6GWQJzCMyhMIfB3AbmcJgjYI6k2/Pu/1fFf+uUBHLgnwr2RTeggL9KMJYb
+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
+
+------=_NextPart_01D386B7.E05F8590
+Content-Location: file:///C:/267BA2D4/Test_files/filelist.xml
+Content-Transfer-Encoding: quoted-printable
+Content-Type: text/xml; charset="utf-8"
+
+<xml xmlns:o=3D"urn:schemas-microsoft-com:office:office">
+ <o:MainFile HRef=3D"../Test.htm"/>
+ <o:File HRef=3D"themedata.thmx"/>
+ <o:File HRef=3D"colorschememapping.xml"/>
+ <o:File HRef=3D"image001.png"/>
+ <o:File HRef=3D"image002.png"/>
+ <o:File HRef=3D"image003.png"/>
+ <o:File HRef=3D"image004.png"/>
+ <o:File HRef=3D"image005.png"/>
+ <o:File HRef=3D"image006.png"/>
+ <o:File HRef=3D"oledata.mso"/>
+ <o:File HRef=3D"filelist.xml"/>
+</xml>
+------=_NextPart_01D386B7.E05F8590--
diff --git a/TestFiles/Presentation.pptx b/TestFiles/Presentation.pptx
new file mode 100644
index 0000000..5c03370
--- /dev/null
+++ b/TestFiles/Presentation.pptx
Binary files differ
diff --git a/TestFiles/RA001-Tracked-Revisions-01.docx b/TestFiles/RA001-Tracked-Revisions-01.docx
new file mode 100644
index 0000000..fd09b86
--- /dev/null
+++ b/TestFiles/RA001-Tracked-Revisions-01.docx
Binary files differ
diff --git a/TestFiles/RA001-Tracked-Revisions-02.docx b/TestFiles/RA001-Tracked-Revisions-02.docx
new file mode 100644
index 0000000..192d2e1
--- /dev/null
+++ b/TestFiles/RA001-Tracked-Revisions-02.docx
Binary files differ
diff --git a/TestFiles/RC/RC001-After1.docx b/TestFiles/RC/RC001-After1.docx
new file mode 100644
index 0000000..6f7b477
--- /dev/null
+++ b/TestFiles/RC/RC001-After1.docx
Binary files differ
diff --git a/TestFiles/RC/RC001-After2.docx b/TestFiles/RC/RC001-After2.docx
new file mode 100644
index 0000000..7068f33
--- /dev/null
+++ b/TestFiles/RC/RC001-After2.docx
Binary files differ
diff --git a/TestFiles/RC/RC001-Before.docx b/TestFiles/RC/RC001-Before.docx
new file mode 100644
index 0000000..80870e9
--- /dev/null
+++ b/TestFiles/RC/RC001-Before.docx
Binary files differ
diff --git a/TestFiles/RC/RC002-Image-After1.docx b/TestFiles/RC/RC002-Image-After1.docx
new file mode 100644
index 0000000..7178c69
--- /dev/null
+++ b/TestFiles/RC/RC002-Image-After1.docx
Binary files differ
diff --git a/TestFiles/RC/RC002-Image.docx b/TestFiles/RC/RC002-Image.docx
new file mode 100644
index 0000000..80870e9
--- /dev/null
+++ b/TestFiles/RC/RC002-Image.docx
Binary files differ
diff --git a/TestFiles/RC/RC003-Multi-Paras-After.docx b/TestFiles/RC/RC003-Multi-Paras-After.docx
new file mode 100644
index 0000000..b527899
--- /dev/null
+++ b/TestFiles/RC/RC003-Multi-Paras-After.docx
Binary files differ
diff --git a/TestFiles/RC/RC003-Multi-Paras.docx b/TestFiles/RC/RC003-Multi-Paras.docx
new file mode 100644
index 0000000..7ee2119
--- /dev/null
+++ b/TestFiles/RC/RC003-Multi-Paras.docx
Binary files differ
diff --git a/TestFiles/RC/RC004-After1.docx b/TestFiles/RC/RC004-After1.docx
new file mode 100644
index 0000000..6b51cf4
--- /dev/null
+++ b/TestFiles/RC/RC004-After1.docx
Binary files differ
diff --git a/TestFiles/RC/RC004-After2.docx b/TestFiles/RC/RC004-After2.docx
new file mode 100644
index 0000000..9b0e666
--- /dev/null
+++ b/TestFiles/RC/RC004-After2.docx
Binary files differ
diff --git a/TestFiles/RC/RC004-Before.docx b/TestFiles/RC/RC004-Before.docx
new file mode 100644
index 0000000..e71a44d
--- /dev/null
+++ b/TestFiles/RC/RC004-Before.docx
Binary files differ
diff --git a/TestFiles/RC/RC005-After1.docx b/TestFiles/RC/RC005-After1.docx
new file mode 100644
index 0000000..476ad76
--- /dev/null
+++ b/TestFiles/RC/RC005-After1.docx
Binary files differ
diff --git a/TestFiles/RC/RC005-Before.docx b/TestFiles/RC/RC005-Before.docx
new file mode 100644
index 0000000..e71a44d
--- /dev/null
+++ b/TestFiles/RC/RC005-Before.docx
Binary files differ
diff --git a/TestFiles/RC/RC006-After1.docx b/TestFiles/RC/RC006-After1.docx
new file mode 100644
index 0000000..06774fd
--- /dev/null
+++ b/TestFiles/RC/RC006-After1.docx
Binary files differ
diff --git a/TestFiles/RC/RC006-Before.docx b/TestFiles/RC/RC006-Before.docx
new file mode 100644
index 0000000..e7ea50c
--- /dev/null
+++ b/TestFiles/RC/RC006-Before.docx
Binary files differ
diff --git a/TestFiles/RC/RC007-Endnotes-After.docx b/TestFiles/RC/RC007-Endnotes-After.docx
new file mode 100644
index 0000000..b3a6f33
--- /dev/null
+++ b/TestFiles/RC/RC007-Endnotes-After.docx
Binary files differ
diff --git a/TestFiles/RC/RC007-Endnotes-Before.docx b/TestFiles/RC/RC007-Endnotes-Before.docx
new file mode 100644
index 0000000..024c139
--- /dev/null
+++ b/TestFiles/RC/RC007-Endnotes-Before.docx
Binary files differ
diff --git a/TestFiles/RP/RP001-Tracked-Revisions-01-Accepted.docx b/TestFiles/RP/RP001-Tracked-Revisions-01-Accepted.docx
new file mode 100644
index 0000000..657016b
--- /dev/null
+++ b/TestFiles/RP/RP001-Tracked-Revisions-01-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP001-Tracked-Revisions-01.docx b/TestFiles/RP/RP001-Tracked-Revisions-01.docx
new file mode 100644
index 0000000..fd09b86
--- /dev/null
+++ b/TestFiles/RP/RP001-Tracked-Revisions-01.docx
Binary files differ
diff --git a/TestFiles/RP/RP001-Tracked-Revisions-02-Accepted.docx b/TestFiles/RP/RP001-Tracked-Revisions-02-Accepted.docx
new file mode 100644
index 0000000..ec69b44
--- /dev/null
+++ b/TestFiles/RP/RP001-Tracked-Revisions-02-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP001-Tracked-Revisions-02.docx b/TestFiles/RP/RP001-Tracked-Revisions-02.docx
new file mode 100644
index 0000000..192d2e1
--- /dev/null
+++ b/TestFiles/RP/RP001-Tracked-Revisions-02.docx
Binary files differ
diff --git a/TestFiles/RP/RP002-Deleted-Text-Accepted.docx b/TestFiles/RP/RP002-Deleted-Text-Accepted.docx
new file mode 100644
index 0000000..6e43cad
--- /dev/null
+++ b/TestFiles/RP/RP002-Deleted-Text-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP002-Deleted-Text-Rejected.docx b/TestFiles/RP/RP002-Deleted-Text-Rejected.docx
new file mode 100644
index 0000000..1407131
--- /dev/null
+++ b/TestFiles/RP/RP002-Deleted-Text-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP002-Deleted-Text.docx b/TestFiles/RP/RP002-Deleted-Text.docx
new file mode 100644
index 0000000..4e793cd
--- /dev/null
+++ b/TestFiles/RP/RP002-Deleted-Text.docx
Binary files differ
diff --git a/TestFiles/RP/RP003-Inserted-Text-Accepted.docx b/TestFiles/RP/RP003-Inserted-Text-Accepted.docx
new file mode 100644
index 0000000..d643661
--- /dev/null
+++ b/TestFiles/RP/RP003-Inserted-Text-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP003-Inserted-Text-Rejected.docx b/TestFiles/RP/RP003-Inserted-Text-Rejected.docx
new file mode 100644
index 0000000..aefd5b5
--- /dev/null
+++ b/TestFiles/RP/RP003-Inserted-Text-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP003-Inserted-Text.docx b/TestFiles/RP/RP003-Inserted-Text.docx
new file mode 100644
index 0000000..fb2ad56
--- /dev/null
+++ b/TestFiles/RP/RP003-Inserted-Text.docx
Binary files differ
diff --git a/TestFiles/RP/RP004-Deleted-Text-in-CC-Accepted.docx b/TestFiles/RP/RP004-Deleted-Text-in-CC-Accepted.docx
new file mode 100644
index 0000000..028baa4
--- /dev/null
+++ b/TestFiles/RP/RP004-Deleted-Text-in-CC-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP004-Deleted-Text-in-CC-Rejected.docx b/TestFiles/RP/RP004-Deleted-Text-in-CC-Rejected.docx
new file mode 100644
index 0000000..a55bd24
--- /dev/null
+++ b/TestFiles/RP/RP004-Deleted-Text-in-CC-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP004-Deleted-Text-in-CC.docx b/TestFiles/RP/RP004-Deleted-Text-in-CC.docx
new file mode 100644
index 0000000..5280220
--- /dev/null
+++ b/TestFiles/RP/RP004-Deleted-Text-in-CC.docx
Binary files differ
diff --git a/TestFiles/RP/RP005-Deleted-Paragraph-Mark-Accepted.docx b/TestFiles/RP/RP005-Deleted-Paragraph-Mark-Accepted.docx
new file mode 100644
index 0000000..9fc95ac
--- /dev/null
+++ b/TestFiles/RP/RP005-Deleted-Paragraph-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP005-Deleted-Paragraph-Mark-Rejected.docx b/TestFiles/RP/RP005-Deleted-Paragraph-Mark-Rejected.docx
new file mode 100644
index 0000000..fec6791
--- /dev/null
+++ b/TestFiles/RP/RP005-Deleted-Paragraph-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP005-Deleted-Paragraph-Mark.docx b/TestFiles/RP/RP005-Deleted-Paragraph-Mark.docx
new file mode 100644
index 0000000..0e5780d
--- /dev/null
+++ b/TestFiles/RP/RP005-Deleted-Paragraph-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP006-Inserted-Paragraph-Mark-Accepted.docx b/TestFiles/RP/RP006-Inserted-Paragraph-Mark-Accepted.docx
new file mode 100644
index 0000000..5b1a6cf
--- /dev/null
+++ b/TestFiles/RP/RP006-Inserted-Paragraph-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP006-Inserted-Paragraph-Mark-Rejected.docx b/TestFiles/RP/RP006-Inserted-Paragraph-Mark-Rejected.docx
new file mode 100644
index 0000000..21aa3bd
--- /dev/null
+++ b/TestFiles/RP/RP006-Inserted-Paragraph-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP006-Inserted-Paragraph-Mark.docx b/TestFiles/RP/RP006-Inserted-Paragraph-Mark.docx
new file mode 100644
index 0000000..17aec0d
--- /dev/null
+++ b/TestFiles/RP/RP006-Inserted-Paragraph-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark-Accepted.docx b/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark-Accepted.docx
new file mode 100644
index 0000000..e9eea8c
--- /dev/null
+++ b/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark-Rejected.docx b/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark-Rejected.docx
new file mode 100644
index 0000000..3772bd9
--- /dev/null
+++ b/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark.docx b/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark.docx
new file mode 100644
index 0000000..5441f26
--- /dev/null
+++ b/TestFiles/RP/RP007-Multiple-Deleted-Para-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark-Accepted.docx b/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark-Accepted.docx
new file mode 100644
index 0000000..c55e9c7
--- /dev/null
+++ b/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark-Rejected.docx b/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark-Rejected.docx
new file mode 100644
index 0000000..8e2fa5f
--- /dev/null
+++ b/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark.docx b/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark.docx
new file mode 100644
index 0000000..a932c9a
--- /dev/null
+++ b/TestFiles/RP/RP008-Multiple-Inserted-Para-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP009-Deleted-Table-Row-Accepted.docx b/TestFiles/RP/RP009-Deleted-Table-Row-Accepted.docx
new file mode 100644
index 0000000..eaa5907
--- /dev/null
+++ b/TestFiles/RP/RP009-Deleted-Table-Row-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP009-Deleted-Table-Row-Rejected.docx b/TestFiles/RP/RP009-Deleted-Table-Row-Rejected.docx
new file mode 100644
index 0000000..7533297
--- /dev/null
+++ b/TestFiles/RP/RP009-Deleted-Table-Row-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP009-Deleted-Table-Row.docx b/TestFiles/RP/RP009-Deleted-Table-Row.docx
new file mode 100644
index 0000000..34906df
--- /dev/null
+++ b/TestFiles/RP/RP009-Deleted-Table-Row.docx
Binary files differ
diff --git a/TestFiles/RP/RP010-Inserted-Table-Row-Accepted.docx b/TestFiles/RP/RP010-Inserted-Table-Row-Accepted.docx
new file mode 100644
index 0000000..9a2fe58
--- /dev/null
+++ b/TestFiles/RP/RP010-Inserted-Table-Row-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP010-Inserted-Table-Row-Rejected.docx b/TestFiles/RP/RP010-Inserted-Table-Row-Rejected.docx
new file mode 100644
index 0000000..65203f8
--- /dev/null
+++ b/TestFiles/RP/RP010-Inserted-Table-Row-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP010-Inserted-Table-Row.docx b/TestFiles/RP/RP010-Inserted-Table-Row.docx
new file mode 100644
index 0000000..e6bdc70
--- /dev/null
+++ b/TestFiles/RP/RP010-Inserted-Table-Row.docx
Binary files differ
diff --git a/TestFiles/RP/RP011-Multiple-Deleted-Rows-Accepted.docx b/TestFiles/RP/RP011-Multiple-Deleted-Rows-Accepted.docx
new file mode 100644
index 0000000..514c626
--- /dev/null
+++ b/TestFiles/RP/RP011-Multiple-Deleted-Rows-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP011-Multiple-Deleted-Rows-Rejected.docx b/TestFiles/RP/RP011-Multiple-Deleted-Rows-Rejected.docx
new file mode 100644
index 0000000..7fd1f72
--- /dev/null
+++ b/TestFiles/RP/RP011-Multiple-Deleted-Rows-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP011-Multiple-Deleted-Rows.docx b/TestFiles/RP/RP011-Multiple-Deleted-Rows.docx
new file mode 100644
index 0000000..fb11fd8
--- /dev/null
+++ b/TestFiles/RP/RP011-Multiple-Deleted-Rows.docx
Binary files differ
diff --git a/TestFiles/RP/RP012-Multiple-Inserted-Rows-Accepted.docx b/TestFiles/RP/RP012-Multiple-Inserted-Rows-Accepted.docx
new file mode 100644
index 0000000..f46d5a7
--- /dev/null
+++ b/TestFiles/RP/RP012-Multiple-Inserted-Rows-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP012-Multiple-Inserted-Rows-Rejected.docx b/TestFiles/RP/RP012-Multiple-Inserted-Rows-Rejected.docx
new file mode 100644
index 0000000..966cab2
--- /dev/null
+++ b/TestFiles/RP/RP012-Multiple-Inserted-Rows-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP012-Multiple-Inserted-Rows.docx b/TestFiles/RP/RP012-Multiple-Inserted-Rows.docx
new file mode 100644
index 0000000..22a6057
--- /dev/null
+++ b/TestFiles/RP/RP012-Multiple-Inserted-Rows.docx
Binary files differ
diff --git a/TestFiles/RP/RP013-Deleted-Math-Control-Char-Accepted.docx b/TestFiles/RP/RP013-Deleted-Math-Control-Char-Accepted.docx
new file mode 100644
index 0000000..3b27b8a
--- /dev/null
+++ b/TestFiles/RP/RP013-Deleted-Math-Control-Char-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP013-Deleted-Math-Control-Char-Rejected.docx b/TestFiles/RP/RP013-Deleted-Math-Control-Char-Rejected.docx
new file mode 100644
index 0000000..71f99c1
--- /dev/null
+++ b/TestFiles/RP/RP013-Deleted-Math-Control-Char-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP013-Deleted-Math-Control-Char.docx b/TestFiles/RP/RP013-Deleted-Math-Control-Char.docx
new file mode 100644
index 0000000..43ba281
--- /dev/null
+++ b/TestFiles/RP/RP013-Deleted-Math-Control-Char.docx
Binary files differ
diff --git a/TestFiles/RP/RP014-Inserted-Math-Control-Char-Accepted.docx b/TestFiles/RP/RP014-Inserted-Math-Control-Char-Accepted.docx
new file mode 100644
index 0000000..e428c39
--- /dev/null
+++ b/TestFiles/RP/RP014-Inserted-Math-Control-Char-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP014-Inserted-Math-Control-Char-Rejected.docx b/TestFiles/RP/RP014-Inserted-Math-Control-Char-Rejected.docx
new file mode 100644
index 0000000..c4b3366
--- /dev/null
+++ b/TestFiles/RP/RP014-Inserted-Math-Control-Char-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP014-Inserted-Math-Control-Char.docx b/TestFiles/RP/RP014-Inserted-Math-Control-Char.docx
new file mode 100644
index 0000000..cc36734
--- /dev/null
+++ b/TestFiles/RP/RP014-Inserted-Math-Control-Char.docx
Binary files differ
diff --git a/TestFiles/RP/RP015-MoveFrom-MoveTo-Accepted.docx b/TestFiles/RP/RP015-MoveFrom-MoveTo-Accepted.docx
new file mode 100644
index 0000000..f94676b
--- /dev/null
+++ b/TestFiles/RP/RP015-MoveFrom-MoveTo-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP015-MoveFrom-MoveTo-Rejected.docx b/TestFiles/RP/RP015-MoveFrom-MoveTo-Rejected.docx
new file mode 100644
index 0000000..e6f1a46
--- /dev/null
+++ b/TestFiles/RP/RP015-MoveFrom-MoveTo-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP015-MoveFrom-MoveTo.docx b/TestFiles/RP/RP015-MoveFrom-MoveTo.docx
new file mode 100644
index 0000000..421fe80
--- /dev/null
+++ b/TestFiles/RP/RP015-MoveFrom-MoveTo.docx
Binary files differ
diff --git a/TestFiles/RP/RP016-Deleted-CC-Accepted.docx b/TestFiles/RP/RP016-Deleted-CC-Accepted.docx
new file mode 100644
index 0000000..3146c75
--- /dev/null
+++ b/TestFiles/RP/RP016-Deleted-CC-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP016-Deleted-CC-Rejected.docx b/TestFiles/RP/RP016-Deleted-CC-Rejected.docx
new file mode 100644
index 0000000..715a9fa
--- /dev/null
+++ b/TestFiles/RP/RP016-Deleted-CC-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP016-Deleted-CC.docx b/TestFiles/RP/RP016-Deleted-CC.docx
new file mode 100644
index 0000000..1e6692b
--- /dev/null
+++ b/TestFiles/RP/RP016-Deleted-CC.docx
Binary files differ
diff --git a/TestFiles/RP/RP017-Inserted-CC-Accepted.docx b/TestFiles/RP/RP017-Inserted-CC-Accepted.docx
new file mode 100644
index 0000000..501368f
--- /dev/null
+++ b/TestFiles/RP/RP017-Inserted-CC-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP017-Inserted-CC-Rejected.docx b/TestFiles/RP/RP017-Inserted-CC-Rejected.docx
new file mode 100644
index 0000000..a9031d0
--- /dev/null
+++ b/TestFiles/RP/RP017-Inserted-CC-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP017-Inserted-CC.docx b/TestFiles/RP/RP017-Inserted-CC.docx
new file mode 100644
index 0000000..21f9c07
--- /dev/null
+++ b/TestFiles/RP/RP017-Inserted-CC.docx
Binary files differ
diff --git a/TestFiles/RP/RP018-MoveFrom-MoveTo-CC-Accepted.docx b/TestFiles/RP/RP018-MoveFrom-MoveTo-CC-Accepted.docx
new file mode 100644
index 0000000..b3231e3
--- /dev/null
+++ b/TestFiles/RP/RP018-MoveFrom-MoveTo-CC-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP018-MoveFrom-MoveTo-CC-Rejected.docx b/TestFiles/RP/RP018-MoveFrom-MoveTo-CC-Rejected.docx
new file mode 100644
index 0000000..60feeae
--- /dev/null
+++ b/TestFiles/RP/RP018-MoveFrom-MoveTo-CC-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP018-MoveFrom-MoveTo-CC.docx b/TestFiles/RP/RP018-MoveFrom-MoveTo-CC.docx
new file mode 100644
index 0000000..67f34a9
--- /dev/null
+++ b/TestFiles/RP/RP018-MoveFrom-MoveTo-CC.docx
Binary files differ
diff --git a/TestFiles/RP/RP019-Deleted-Field-Code-Accepted.docx b/TestFiles/RP/RP019-Deleted-Field-Code-Accepted.docx
new file mode 100644
index 0000000..ff34adc
--- /dev/null
+++ b/TestFiles/RP/RP019-Deleted-Field-Code-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP019-Deleted-Field-Code-Rejected.docx b/TestFiles/RP/RP019-Deleted-Field-Code-Rejected.docx
new file mode 100644
index 0000000..ae3ee35
--- /dev/null
+++ b/TestFiles/RP/RP019-Deleted-Field-Code-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP019-Deleted-Field-Code.docx b/TestFiles/RP/RP019-Deleted-Field-Code.docx
new file mode 100644
index 0000000..7be72d5
--- /dev/null
+++ b/TestFiles/RP/RP019-Deleted-Field-Code.docx
Binary files differ
diff --git a/TestFiles/RP/RP020-Inserted-Field-Code-Accepted.docx b/TestFiles/RP/RP020-Inserted-Field-Code-Accepted.docx
new file mode 100644
index 0000000..c892f11
--- /dev/null
+++ b/TestFiles/RP/RP020-Inserted-Field-Code-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP020-Inserted-Field-Code-Rejected.docx b/TestFiles/RP/RP020-Inserted-Field-Code-Rejected.docx
new file mode 100644
index 0000000..d854aba
--- /dev/null
+++ b/TestFiles/RP/RP020-Inserted-Field-Code-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP020-Inserted-Field-Code.docx b/TestFiles/RP/RP020-Inserted-Field-Code.docx
new file mode 100644
index 0000000..81f34ca
--- /dev/null
+++ b/TestFiles/RP/RP020-Inserted-Field-Code.docx
Binary files differ
diff --git a/TestFiles/RP/RP021-Inserted-Numbering-Properties-Accepted.docx b/TestFiles/RP/RP021-Inserted-Numbering-Properties-Accepted.docx
new file mode 100644
index 0000000..1029cf8
--- /dev/null
+++ b/TestFiles/RP/RP021-Inserted-Numbering-Properties-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP021-Inserted-Numbering-Properties-Rejected.docx b/TestFiles/RP/RP021-Inserted-Numbering-Properties-Rejected.docx
new file mode 100644
index 0000000..c82016d
--- /dev/null
+++ b/TestFiles/RP/RP021-Inserted-Numbering-Properties-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP021-Inserted-Numbering-Properties.docx b/TestFiles/RP/RP021-Inserted-Numbering-Properties.docx
new file mode 100644
index 0000000..336a2be
--- /dev/null
+++ b/TestFiles/RP/RP021-Inserted-Numbering-Properties.docx
Binary files differ
diff --git a/TestFiles/RP/RP022-NumberingChange-Accepted.docx b/TestFiles/RP/RP022-NumberingChange-Accepted.docx
new file mode 100644
index 0000000..ad593c0
--- /dev/null
+++ b/TestFiles/RP/RP022-NumberingChange-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP022-NumberingChange-Rejected.docx b/TestFiles/RP/RP022-NumberingChange-Rejected.docx
new file mode 100644
index 0000000..4370198
--- /dev/null
+++ b/TestFiles/RP/RP022-NumberingChange-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP022-NumberingChange.docx b/TestFiles/RP/RP022-NumberingChange.docx
new file mode 100644
index 0000000..33cbef6
--- /dev/null
+++ b/TestFiles/RP/RP022-NumberingChange.docx
Binary files differ
diff --git a/TestFiles/RP/RP023-NumberingChange-Accepted.docx b/TestFiles/RP/RP023-NumberingChange-Accepted.docx
new file mode 100644
index 0000000..d81004c
--- /dev/null
+++ b/TestFiles/RP/RP023-NumberingChange-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP023-NumberingChange-Rejected.docx b/TestFiles/RP/RP023-NumberingChange-Rejected.docx
new file mode 100644
index 0000000..2e76c78
--- /dev/null
+++ b/TestFiles/RP/RP023-NumberingChange-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP023-NumberingChange.docx b/TestFiles/RP/RP023-NumberingChange.docx
new file mode 100644
index 0000000..e921dc9
--- /dev/null
+++ b/TestFiles/RP/RP023-NumberingChange.docx
Binary files differ
diff --git a/TestFiles/RP/RP024-ParagraphMark-rPr-Change-Accepted.docx b/TestFiles/RP/RP024-ParagraphMark-rPr-Change-Accepted.docx
new file mode 100644
index 0000000..a17f5af
--- /dev/null
+++ b/TestFiles/RP/RP024-ParagraphMark-rPr-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP024-ParagraphMark-rPr-Change-Rejected.docx b/TestFiles/RP/RP024-ParagraphMark-rPr-Change-Rejected.docx
new file mode 100644
index 0000000..78c3512
--- /dev/null
+++ b/TestFiles/RP/RP024-ParagraphMark-rPr-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP024-ParagraphMark-rPr-Change.docx b/TestFiles/RP/RP024-ParagraphMark-rPr-Change.docx
new file mode 100644
index 0000000..e9d6d66
--- /dev/null
+++ b/TestFiles/RP/RP024-ParagraphMark-rPr-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP025-Paragraph-Props-Change-Accepted.docx b/TestFiles/RP/RP025-Paragraph-Props-Change-Accepted.docx
new file mode 100644
index 0000000..aa9bbf2
--- /dev/null
+++ b/TestFiles/RP/RP025-Paragraph-Props-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP025-Paragraph-Props-Change-Rejected.docx b/TestFiles/RP/RP025-Paragraph-Props-Change-Rejected.docx
new file mode 100644
index 0000000..525667b
--- /dev/null
+++ b/TestFiles/RP/RP025-Paragraph-Props-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP025-Paragraph-Props-Change.docx b/TestFiles/RP/RP025-Paragraph-Props-Change.docx
new file mode 100644
index 0000000..ccf93cd
--- /dev/null
+++ b/TestFiles/RP/RP025-Paragraph-Props-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP026-NumberingChange-Accepted.docx b/TestFiles/RP/RP026-NumberingChange-Accepted.docx
new file mode 100644
index 0000000..b243a8f
--- /dev/null
+++ b/TestFiles/RP/RP026-NumberingChange-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP026-NumberingChange-Rejected.docx b/TestFiles/RP/RP026-NumberingChange-Rejected.docx
new file mode 100644
index 0000000..1b0fac1
--- /dev/null
+++ b/TestFiles/RP/RP026-NumberingChange-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP026-NumberingChange.docx b/TestFiles/RP/RP026-NumberingChange.docx
new file mode 100644
index 0000000..4866137
--- /dev/null
+++ b/TestFiles/RP/RP026-NumberingChange.docx
Binary files differ
diff --git a/TestFiles/RP/RP027-Change-Section-Accepted.docx b/TestFiles/RP/RP027-Change-Section-Accepted.docx
new file mode 100644
index 0000000..fca171a
--- /dev/null
+++ b/TestFiles/RP/RP027-Change-Section-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP027-Change-Section-Rejected.docx b/TestFiles/RP/RP027-Change-Section-Rejected.docx
new file mode 100644
index 0000000..84402ba
--- /dev/null
+++ b/TestFiles/RP/RP027-Change-Section-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP027-Change-Section.docx b/TestFiles/RP/RP027-Change-Section.docx
new file mode 100644
index 0000000..0fb6c5a
--- /dev/null
+++ b/TestFiles/RP/RP027-Change-Section.docx
Binary files differ
diff --git a/TestFiles/RP/RP028-Table-Grid-Change-Accepted.docx b/TestFiles/RP/RP028-Table-Grid-Change-Accepted.docx
new file mode 100644
index 0000000..046869b
--- /dev/null
+++ b/TestFiles/RP/RP028-Table-Grid-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP028-Table-Grid-Change-Rejected.docx b/TestFiles/RP/RP028-Table-Grid-Change-Rejected.docx
new file mode 100644
index 0000000..c86dee3
--- /dev/null
+++ b/TestFiles/RP/RP028-Table-Grid-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP028-Table-Grid-Change.docx b/TestFiles/RP/RP028-Table-Grid-Change.docx
new file mode 100644
index 0000000..aa2ac27
--- /dev/null
+++ b/TestFiles/RP/RP028-Table-Grid-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP029-Table-Row-Props-Change-Accepted.docx b/TestFiles/RP/RP029-Table-Row-Props-Change-Accepted.docx
new file mode 100644
index 0000000..bacc008
--- /dev/null
+++ b/TestFiles/RP/RP029-Table-Row-Props-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP029-Table-Row-Props-Change-Rejected.docx b/TestFiles/RP/RP029-Table-Row-Props-Change-Rejected.docx
new file mode 100644
index 0000000..6943646
--- /dev/null
+++ b/TestFiles/RP/RP029-Table-Row-Props-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP029-Table-Row-Props-Change.docx b/TestFiles/RP/RP029-Table-Row-Props-Change.docx
new file mode 100644
index 0000000..154ba71
--- /dev/null
+++ b/TestFiles/RP/RP029-Table-Row-Props-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP030-Table-Row-Props-Change-Accepted.docx b/TestFiles/RP/RP030-Table-Row-Props-Change-Accepted.docx
new file mode 100644
index 0000000..c939bae
--- /dev/null
+++ b/TestFiles/RP/RP030-Table-Row-Props-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP030-Table-Row-Props-Change-Rejected.docx b/TestFiles/RP/RP030-Table-Row-Props-Change-Rejected.docx
new file mode 100644
index 0000000..695af6d
--- /dev/null
+++ b/TestFiles/RP/RP030-Table-Row-Props-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP030-Table-Row-Props-Change.docx b/TestFiles/RP/RP030-Table-Row-Props-Change.docx
new file mode 100644
index 0000000..455c77a
--- /dev/null
+++ b/TestFiles/RP/RP030-Table-Row-Props-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP031-Table-Prop-Change-Accepted.docx b/TestFiles/RP/RP031-Table-Prop-Change-Accepted.docx
new file mode 100644
index 0000000..ef59276
--- /dev/null
+++ b/TestFiles/RP/RP031-Table-Prop-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP031-Table-Prop-Change-Rejected.docx b/TestFiles/RP/RP031-Table-Prop-Change-Rejected.docx
new file mode 100644
index 0000000..0e28bd3
--- /dev/null
+++ b/TestFiles/RP/RP031-Table-Prop-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP031-Table-Prop-Change.docx b/TestFiles/RP/RP031-Table-Prop-Change.docx
new file mode 100644
index 0000000..8d6fa06
--- /dev/null
+++ b/TestFiles/RP/RP031-Table-Prop-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP032-Table-Prop-Change-Accepted.docx b/TestFiles/RP/RP032-Table-Prop-Change-Accepted.docx
new file mode 100644
index 0000000..2150005
--- /dev/null
+++ b/TestFiles/RP/RP032-Table-Prop-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP032-Table-Prop-Change-Rejected.docx b/TestFiles/RP/RP032-Table-Prop-Change-Rejected.docx
new file mode 100644
index 0000000..453fbb9
--- /dev/null
+++ b/TestFiles/RP/RP032-Table-Prop-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP032-Table-Prop-Change.docx b/TestFiles/RP/RP032-Table-Prop-Change.docx
new file mode 100644
index 0000000..f3564a4
--- /dev/null
+++ b/TestFiles/RP/RP032-Table-Prop-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP033-Table-Prop-Ex-Change-Accepted.docx b/TestFiles/RP/RP033-Table-Prop-Ex-Change-Accepted.docx
new file mode 100644
index 0000000..afd53d9
--- /dev/null
+++ b/TestFiles/RP/RP033-Table-Prop-Ex-Change-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP033-Table-Prop-Ex-Change-Rejected.docx b/TestFiles/RP/RP033-Table-Prop-Ex-Change-Rejected.docx
new file mode 100644
index 0000000..dd44ac6
--- /dev/null
+++ b/TestFiles/RP/RP033-Table-Prop-Ex-Change-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP033-Table-Prop-Ex-Change.docx b/TestFiles/RP/RP033-Table-Prop-Ex-Change.docx
new file mode 100644
index 0000000..3b21024
--- /dev/null
+++ b/TestFiles/RP/RP033-Table-Prop-Ex-Change.docx
Binary files differ
diff --git a/TestFiles/RP/RP034-Deleted-Cells-Accepted.docx b/TestFiles/RP/RP034-Deleted-Cells-Accepted.docx
new file mode 100644
index 0000000..c49baf4
--- /dev/null
+++ b/TestFiles/RP/RP034-Deleted-Cells-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP034-Deleted-Cells-Rejected.docx b/TestFiles/RP/RP034-Deleted-Cells-Rejected.docx
new file mode 100644
index 0000000..65cf4e0
--- /dev/null
+++ b/TestFiles/RP/RP034-Deleted-Cells-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP034-Deleted-Cells.docx b/TestFiles/RP/RP034-Deleted-Cells.docx
new file mode 100644
index 0000000..01b4a33
--- /dev/null
+++ b/TestFiles/RP/RP034-Deleted-Cells.docx
Binary files differ
diff --git a/TestFiles/RP/RP035-Inserted-Cells-Accepted.docx b/TestFiles/RP/RP035-Inserted-Cells-Accepted.docx
new file mode 100644
index 0000000..07c2e23
--- /dev/null
+++ b/TestFiles/RP/RP035-Inserted-Cells-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP035-Inserted-Cells-Rejected.docx b/TestFiles/RP/RP035-Inserted-Cells-Rejected.docx
new file mode 100644
index 0000000..1085ad9
--- /dev/null
+++ b/TestFiles/RP/RP035-Inserted-Cells-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP035-Inserted-Cells.docx b/TestFiles/RP/RP035-Inserted-Cells.docx
new file mode 100644
index 0000000..a44e325
--- /dev/null
+++ b/TestFiles/RP/RP035-Inserted-Cells.docx
Binary files differ
diff --git a/TestFiles/RP/RP036-Vert-Merged-Cells-Accepted.docx b/TestFiles/RP/RP036-Vert-Merged-Cells-Accepted.docx
new file mode 100644
index 0000000..5b0f45f
--- /dev/null
+++ b/TestFiles/RP/RP036-Vert-Merged-Cells-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP036-Vert-Merged-Cells-Rejected.docx b/TestFiles/RP/RP036-Vert-Merged-Cells-Rejected.docx
new file mode 100644
index 0000000..120881c
--- /dev/null
+++ b/TestFiles/RP/RP036-Vert-Merged-Cells-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP036-Vert-Merged-Cells.docx b/TestFiles/RP/RP036-Vert-Merged-Cells.docx
new file mode 100644
index 0000000..56e33dd
--- /dev/null
+++ b/TestFiles/RP/RP036-Vert-Merged-Cells.docx
Binary files differ
diff --git a/TestFiles/RP/RP037-Changed-Style-Para-Props-Accepted.docx b/TestFiles/RP/RP037-Changed-Style-Para-Props-Accepted.docx
new file mode 100644
index 0000000..9837f09
--- /dev/null
+++ b/TestFiles/RP/RP037-Changed-Style-Para-Props-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP037-Changed-Style-Para-Props-Rejected.docx b/TestFiles/RP/RP037-Changed-Style-Para-Props-Rejected.docx
new file mode 100644
index 0000000..ea7d17c
--- /dev/null
+++ b/TestFiles/RP/RP037-Changed-Style-Para-Props-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP037-Changed-Style-Para-Props.docx b/TestFiles/RP/RP037-Changed-Style-Para-Props.docx
new file mode 100644
index 0000000..7716354
--- /dev/null
+++ b/TestFiles/RP/RP037-Changed-Style-Para-Props.docx
Binary files differ
diff --git a/TestFiles/RP/RP038-Inserted-Paras-at-End-Accepted.docx b/TestFiles/RP/RP038-Inserted-Paras-at-End-Accepted.docx
new file mode 100644
index 0000000..2aae0e4
--- /dev/null
+++ b/TestFiles/RP/RP038-Inserted-Paras-at-End-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP038-Inserted-Paras-at-End-Rejected.docx b/TestFiles/RP/RP038-Inserted-Paras-at-End-Rejected.docx
new file mode 100644
index 0000000..47372be
--- /dev/null
+++ b/TestFiles/RP/RP038-Inserted-Paras-at-End-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP038-Inserted-Paras-at-End.docx b/TestFiles/RP/RP038-Inserted-Paras-at-End.docx
new file mode 100644
index 0000000..a68b83c
--- /dev/null
+++ b/TestFiles/RP/RP038-Inserted-Paras-at-End.docx
Binary files differ
diff --git a/TestFiles/RP/RP039-Inserted-Paras-at-End-Accepted.docx b/TestFiles/RP/RP039-Inserted-Paras-at-End-Accepted.docx
new file mode 100644
index 0000000..bae6320
--- /dev/null
+++ b/TestFiles/RP/RP039-Inserted-Paras-at-End-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP039-Inserted-Paras-at-End-Rejected.docx b/TestFiles/RP/RP039-Inserted-Paras-at-End-Rejected.docx
new file mode 100644
index 0000000..412b04d
--- /dev/null
+++ b/TestFiles/RP/RP039-Inserted-Paras-at-End-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP039-Inserted-Paras-at-End.docx b/TestFiles/RP/RP039-Inserted-Paras-at-End.docx
new file mode 100644
index 0000000..5e8f395
--- /dev/null
+++ b/TestFiles/RP/RP039-Inserted-Paras-at-End.docx
Binary files differ
diff --git a/TestFiles/RP/RP040-Deleted-Paras-at-End-Accepted.docx b/TestFiles/RP/RP040-Deleted-Paras-at-End-Accepted.docx
new file mode 100644
index 0000000..d7a7b44
--- /dev/null
+++ b/TestFiles/RP/RP040-Deleted-Paras-at-End-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP040-Deleted-Paras-at-End-Rejected.docx b/TestFiles/RP/RP040-Deleted-Paras-at-End-Rejected.docx
new file mode 100644
index 0000000..26417e0
--- /dev/null
+++ b/TestFiles/RP/RP040-Deleted-Paras-at-End-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP040-Deleted-Paras-at-End.docx b/TestFiles/RP/RP040-Deleted-Paras-at-End.docx
new file mode 100644
index 0000000..82f1ebd
--- /dev/null
+++ b/TestFiles/RP/RP040-Deleted-Paras-at-End.docx
Binary files differ
diff --git a/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End-Accepted.docx b/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End-Accepted.docx
new file mode 100644
index 0000000..42cb7c2
--- /dev/null
+++ b/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End-Rejected.docx b/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End-Rejected.docx
new file mode 100644
index 0000000..05d4d8c
--- /dev/null
+++ b/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End.docx b/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End.docx
new file mode 100644
index 0000000..03c601e
--- /dev/null
+++ b/TestFiles/RP/RP041-Cell-With-Empty-Paras-at-End.docx
Binary files differ
diff --git a/TestFiles/RP/RP042-Deleted-Para-Mark-at-End-Accepted.docx b/TestFiles/RP/RP042-Deleted-Para-Mark-at-End-Accepted.docx
new file mode 100644
index 0000000..8d22792
--- /dev/null
+++ b/TestFiles/RP/RP042-Deleted-Para-Mark-at-End-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP042-Deleted-Para-Mark-at-End-Rejected.docx b/TestFiles/RP/RP042-Deleted-Para-Mark-at-End-Rejected.docx
new file mode 100644
index 0000000..36cad99
--- /dev/null
+++ b/TestFiles/RP/RP042-Deleted-Para-Mark-at-End-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP042-Deleted-Para-Mark-at-End.docx b/TestFiles/RP/RP042-Deleted-Para-Mark-at-End.docx
new file mode 100644
index 0000000..694b5e6
--- /dev/null
+++ b/TestFiles/RP/RP042-Deleted-Para-Mark-at-End.docx
Binary files differ
diff --git a/TestFiles/RP/RP043-MERGEFORMAT-Field-Code-Accepted.docx b/TestFiles/RP/RP043-MERGEFORMAT-Field-Code-Accepted.docx
new file mode 100644
index 0000000..f277fe7
--- /dev/null
+++ b/TestFiles/RP/RP043-MERGEFORMAT-Field-Code-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP043-MERGEFORMAT-Field-Code-Rejected.docx b/TestFiles/RP/RP043-MERGEFORMAT-Field-Code-Rejected.docx
new file mode 100644
index 0000000..240e73a
--- /dev/null
+++ b/TestFiles/RP/RP043-MERGEFORMAT-Field-Code-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP043-MERGEFORMAT-Field-Code.docx b/TestFiles/RP/RP043-MERGEFORMAT-Field-Code.docx
new file mode 100644
index 0000000..6394d4b
--- /dev/null
+++ b/TestFiles/RP/RP043-MERGEFORMAT-Field-Code.docx
Binary files differ
diff --git a/TestFiles/RP/RP044-MERGEFORMAT-Field-Code-Accepted.docx b/TestFiles/RP/RP044-MERGEFORMAT-Field-Code-Accepted.docx
new file mode 100644
index 0000000..74f9eb8
--- /dev/null
+++ b/TestFiles/RP/RP044-MERGEFORMAT-Field-Code-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP044-MERGEFORMAT-Field-Code-Rejected.docx b/TestFiles/RP/RP044-MERGEFORMAT-Field-Code-Rejected.docx
new file mode 100644
index 0000000..2a930ac
--- /dev/null
+++ b/TestFiles/RP/RP044-MERGEFORMAT-Field-Code-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP044-MERGEFORMAT-Field-Code.docx b/TestFiles/RP/RP044-MERGEFORMAT-Field-Code.docx
new file mode 100644
index 0000000..aff8aab
--- /dev/null
+++ b/TestFiles/RP/RP044-MERGEFORMAT-Field-Code.docx
Binary files differ
diff --git a/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End-Accepted.docx b/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End-Accepted.docx
new file mode 100644
index 0000000..a421fef
--- /dev/null
+++ b/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End-Rejected.docx b/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End-Rejected.docx
new file mode 100644
index 0000000..820c60f
--- /dev/null
+++ b/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End.docx b/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End.docx
new file mode 100644
index 0000000..f75e229
--- /dev/null
+++ b/TestFiles/RP/RP045-One-and-Half-Deleted-Lines-at-End.docx
Binary files differ
diff --git a/TestFiles/RP/RP046-Consecutive-Deleted-Ranges-Accepted.docx b/TestFiles/RP/RP046-Consecutive-Deleted-Ranges-Accepted.docx
new file mode 100644
index 0000000..9bb410d
--- /dev/null
+++ b/TestFiles/RP/RP046-Consecutive-Deleted-Ranges-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP046-Consecutive-Deleted-Ranges-Rejected.docx b/TestFiles/RP/RP046-Consecutive-Deleted-Ranges-Rejected.docx
new file mode 100644
index 0000000..7e290ea
--- /dev/null
+++ b/TestFiles/RP/RP046-Consecutive-Deleted-Ranges-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP046-Consecutive-Deleted-Ranges.docx b/TestFiles/RP/RP046-Consecutive-Deleted-Ranges.docx
new file mode 100644
index 0000000..73823db
--- /dev/null
+++ b/TestFiles/RP/RP046-Consecutive-Deleted-Ranges.docx
Binary files differ
diff --git a/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark-Accepted.docx b/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark-Accepted.docx
new file mode 100644
index 0000000..3ed5b01
--- /dev/null
+++ b/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark-Rejected.docx b/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark-Rejected.docx
new file mode 100644
index 0000000..3ed42ff
--- /dev/null
+++ b/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark.docx b/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark.docx
new file mode 100644
index 0000000..00cbfb4
--- /dev/null
+++ b/TestFiles/RP/RP047-Inserted-and-Deleted-Paragraph-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark-Accepted.docx b/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark-Accepted.docx
new file mode 100644
index 0000000..0d460c3
--- /dev/null
+++ b/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark-Rejected.docx b/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark-Rejected.docx
new file mode 100644
index 0000000..5b5dc59
--- /dev/null
+++ b/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark.docx b/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark.docx
new file mode 100644
index 0000000..6c03efd
--- /dev/null
+++ b/TestFiles/RP/RP048-Deleted-Inserted-Para-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP049-Deleted-Para-Before-Table-Accepted.docx b/TestFiles/RP/RP049-Deleted-Para-Before-Table-Accepted.docx
new file mode 100644
index 0000000..ed308d1
--- /dev/null
+++ b/TestFiles/RP/RP049-Deleted-Para-Before-Table-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP049-Deleted-Para-Before-Table-Rejected.docx b/TestFiles/RP/RP049-Deleted-Para-Before-Table-Rejected.docx
new file mode 100644
index 0000000..6b0e751
--- /dev/null
+++ b/TestFiles/RP/RP049-Deleted-Para-Before-Table-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP049-Deleted-Para-Before-Table.docx b/TestFiles/RP/RP049-Deleted-Para-Before-Table.docx
new file mode 100644
index 0000000..fd3d486
--- /dev/null
+++ b/TestFiles/RP/RP049-Deleted-Para-Before-Table.docx
Binary files differ
diff --git a/TestFiles/RP/RP050-Deleted-Footnote-Accepted.docx b/TestFiles/RP/RP050-Deleted-Footnote-Accepted.docx
new file mode 100644
index 0000000..c7a7cdf
--- /dev/null
+++ b/TestFiles/RP/RP050-Deleted-Footnote-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP050-Deleted-Footnote-Rejected.docx b/TestFiles/RP/RP050-Deleted-Footnote-Rejected.docx
new file mode 100644
index 0000000..df128d7
--- /dev/null
+++ b/TestFiles/RP/RP050-Deleted-Footnote-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP050-Deleted-Footnote.docx b/TestFiles/RP/RP050-Deleted-Footnote.docx
new file mode 100644
index 0000000..96e2dc3
--- /dev/null
+++ b/TestFiles/RP/RP050-Deleted-Footnote.docx
Binary files differ
diff --git a/TestFiles/RP/RP051-Arabic-Accepted.docx b/TestFiles/RP/RP051-Arabic-Accepted.docx
new file mode 100644
index 0000000..42e4009
--- /dev/null
+++ b/TestFiles/RP/RP051-Arabic-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP051-Arabic-Rejected.docx b/TestFiles/RP/RP051-Arabic-Rejected.docx
new file mode 100644
index 0000000..ce801e9
--- /dev/null
+++ b/TestFiles/RP/RP051-Arabic-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP051-Arabic.docx b/TestFiles/RP/RP051-Arabic.docx
new file mode 100644
index 0000000..810a32f
--- /dev/null
+++ b/TestFiles/RP/RP051-Arabic.docx
Binary files differ
diff --git a/TestFiles/RP/RP052-Deleted-Para-Mark-Accepted.docx b/TestFiles/RP/RP052-Deleted-Para-Mark-Accepted.docx
new file mode 100644
index 0000000..d0b6af2
--- /dev/null
+++ b/TestFiles/RP/RP052-Deleted-Para-Mark-Accepted.docx
Binary files differ
diff --git a/TestFiles/RP/RP052-Deleted-Para-Mark-Rejected.docx b/TestFiles/RP/RP052-Deleted-Para-Mark-Rejected.docx
new file mode 100644
index 0000000..7d4dc00
--- /dev/null
+++ b/TestFiles/RP/RP052-Deleted-Para-Mark-Rejected.docx
Binary files differ
diff --git a/TestFiles/RP/RP052-Deleted-Para-Mark.docx b/TestFiles/RP/RP052-Deleted-Para-Mark.docx
new file mode 100644
index 0000000..94521d8
--- /dev/null
+++ b/TestFiles/RP/RP052-Deleted-Para-Mark.docx
Binary files differ
diff --git a/TestFiles/RP/RP999-Table.docx b/TestFiles/RP/RP999-Table.docx
new file mode 100644
index 0000000..98bfb40
--- /dev/null
+++ b/TestFiles/RP/RP999-Table.docx
Binary files differ
diff --git a/TestFiles/SH001-Table.xlsx b/TestFiles/SH001-Table.xlsx
new file mode 100644
index 0000000..139a02c
--- /dev/null
+++ b/TestFiles/SH001-Table.xlsx
Binary files differ
diff --git a/TestFiles/SH002-TwoTablesTwoSheets.xlsx b/TestFiles/SH002-TwoTablesTwoSheets.xlsx
new file mode 100644
index 0000000..a26e02f
--- /dev/null
+++ b/TestFiles/SH002-TwoTablesTwoSheets.xlsx
Binary files differ
diff --git a/TestFiles/SH003-TableWithDateInFirstColumn.xlsx b/TestFiles/SH003-TableWithDateInFirstColumn.xlsx
new file mode 100644
index 0000000..3e99ac6
--- /dev/null
+++ b/TestFiles/SH003-TableWithDateInFirstColumn.xlsx
Binary files differ
diff --git a/TestFiles/SH004-TableAtOffsetLocation.xlsx b/TestFiles/SH004-TableAtOffsetLocation.xlsx
new file mode 100644
index 0000000..7e04bc7
--- /dev/null
+++ b/TestFiles/SH004-TableAtOffsetLocation.xlsx
Binary files differ
diff --git a/TestFiles/SH005-Table-With-SharedStrings.xlsx b/TestFiles/SH005-Table-With-SharedStrings.xlsx
new file mode 100644
index 0000000..0ad1fe5
--- /dev/null
+++ b/TestFiles/SH005-Table-With-SharedStrings.xlsx
Binary files differ
diff --git a/TestFiles/SH006-Table-No-SharedStrings.xlsx b/TestFiles/SH006-Table-No-SharedStrings.xlsx
new file mode 100644
index 0000000..ecf16a4
--- /dev/null
+++ b/TestFiles/SH006-Table-No-SharedStrings.xlsx
Binary files differ
diff --git a/TestFiles/SH007-One-Cell-Table.xlsx b/TestFiles/SH007-One-Cell-Table.xlsx
new file mode 100644
index 0000000..7aaf9c6
--- /dev/null
+++ b/TestFiles/SH007-One-Cell-Table.xlsx
Binary files differ
diff --git a/TestFiles/SH008-Table-With-Tall-Row.xlsx b/TestFiles/SH008-Table-With-Tall-Row.xlsx
new file mode 100644
index 0000000..03cfbb9
--- /dev/null
+++ b/TestFiles/SH008-Table-With-Tall-Row.xlsx
Binary files differ
diff --git a/TestFiles/SH009-Table-With-Wide-Column.xlsx b/TestFiles/SH009-Table-With-Wide-Column.xlsx
new file mode 100644
index 0000000..8faf19f
--- /dev/null
+++ b/TestFiles/SH009-Table-With-Wide-Column.xlsx
Binary files differ
diff --git a/TestFiles/SH101-SimpleFormats.xlsx b/TestFiles/SH101-SimpleFormats.xlsx
new file mode 100644
index 0000000..70f7ebb
--- /dev/null
+++ b/TestFiles/SH101-SimpleFormats.xlsx
Binary files differ
diff --git a/TestFiles/SH102-9-x-9.xlsx b/TestFiles/SH102-9-x-9.xlsx
new file mode 100644
index 0000000..049afeb
--- /dev/null
+++ b/TestFiles/SH102-9-x-9.xlsx
Binary files differ
diff --git a/TestFiles/SH103-No-SharedString.xlsx b/TestFiles/SH103-No-SharedString.xlsx
new file mode 100644
index 0000000..4178be7
--- /dev/null
+++ b/TestFiles/SH103-No-SharedString.xlsx
Binary files differ
diff --git a/TestFiles/SH104-With-SharedString.xlsx b/TestFiles/SH104-With-SharedString.xlsx
new file mode 100644
index 0000000..ef09990
--- /dev/null
+++ b/TestFiles/SH104-With-SharedString.xlsx
Binary files differ
diff --git a/TestFiles/SH105-No-SharedString.xlsx b/TestFiles/SH105-No-SharedString.xlsx
new file mode 100644
index 0000000..779a0b4
--- /dev/null
+++ b/TestFiles/SH105-No-SharedString.xlsx
Binary files differ
diff --git a/TestFiles/SH106-9-x-9-Formatted.xlsx b/TestFiles/SH106-9-x-9-Formatted.xlsx
new file mode 100644
index 0000000..0e28e31
--- /dev/null
+++ b/TestFiles/SH106-9-x-9-Formatted.xlsx
Binary files differ
diff --git a/TestFiles/SH107-9-x-9-Formatted-Table.xlsx b/TestFiles/SH107-9-x-9-Formatted-Table.xlsx
new file mode 100644
index 0000000..c6dbd34
--- /dev/null
+++ b/TestFiles/SH107-9-x-9-Formatted-Table.xlsx
Binary files differ
diff --git a/TestFiles/SH108-SimpleFormattedCell.xlsx b/TestFiles/SH108-SimpleFormattedCell.xlsx
new file mode 100644
index 0000000..78924e2
--- /dev/null
+++ b/TestFiles/SH108-SimpleFormattedCell.xlsx
Binary files differ
diff --git a/TestFiles/SH109-CellWithBorder.xlsx b/TestFiles/SH109-CellWithBorder.xlsx
new file mode 100644
index 0000000..986ab1a
--- /dev/null
+++ b/TestFiles/SH109-CellWithBorder.xlsx
Binary files differ
diff --git a/TestFiles/SH110-CellWithMasterStyle.xlsx b/TestFiles/SH110-CellWithMasterStyle.xlsx
new file mode 100644
index 0000000..1111035
--- /dev/null
+++ b/TestFiles/SH110-CellWithMasterStyle.xlsx
Binary files differ
diff --git a/TestFiles/SH111-ChangedDefaultColumnWidth.xlsx b/TestFiles/SH111-ChangedDefaultColumnWidth.xlsx
new file mode 100644
index 0000000..bfa0b03
--- /dev/null
+++ b/TestFiles/SH111-ChangedDefaultColumnWidth.xlsx
Binary files differ
diff --git a/TestFiles/SH112-NotVertMergedCell.xlsx b/TestFiles/SH112-NotVertMergedCell.xlsx
new file mode 100644
index 0000000..e2c1981
--- /dev/null
+++ b/TestFiles/SH112-NotVertMergedCell.xlsx
Binary files differ
diff --git a/TestFiles/SH113-VertMergedCell.xlsx b/TestFiles/SH113-VertMergedCell.xlsx
new file mode 100644
index 0000000..ae200e2
--- /dev/null
+++ b/TestFiles/SH113-VertMergedCell.xlsx
Binary files differ
diff --git a/TestFiles/SH114-Centered-Cell.xlsx b/TestFiles/SH114-Centered-Cell.xlsx
new file mode 100644
index 0000000..e092b5d
--- /dev/null
+++ b/TestFiles/SH114-Centered-Cell.xlsx
Binary files differ
diff --git a/TestFiles/SH115-DigitsToRight.xlsx b/TestFiles/SH115-DigitsToRight.xlsx
new file mode 100644
index 0000000..fcefedb
--- /dev/null
+++ b/TestFiles/SH115-DigitsToRight.xlsx
Binary files differ
diff --git a/TestFiles/SH116-FmtNumId-1.xlsx b/TestFiles/SH116-FmtNumId-1.xlsx
new file mode 100644
index 0000000..5370d8c
--- /dev/null
+++ b/TestFiles/SH116-FmtNumId-1.xlsx
Binary files differ
diff --git a/TestFiles/SH117-FmtNumId-2.xlsx b/TestFiles/SH117-FmtNumId-2.xlsx
new file mode 100644
index 0000000..4650658
--- /dev/null
+++ b/TestFiles/SH117-FmtNumId-2.xlsx
Binary files differ
diff --git a/TestFiles/SH118-FmtNumId-3.xlsx b/TestFiles/SH118-FmtNumId-3.xlsx
new file mode 100644
index 0000000..143d897
--- /dev/null
+++ b/TestFiles/SH118-FmtNumId-3.xlsx
Binary files differ
diff --git a/TestFiles/SH119-FmtNumId-4.xlsx b/TestFiles/SH119-FmtNumId-4.xlsx
new file mode 100644
index 0000000..4454a2c
--- /dev/null
+++ b/TestFiles/SH119-FmtNumId-4.xlsx
Binary files differ
diff --git a/TestFiles/SH120-FmtNumId-9.xlsx b/TestFiles/SH120-FmtNumId-9.xlsx
new file mode 100644
index 0000000..a6bbd71
--- /dev/null
+++ b/TestFiles/SH120-FmtNumId-9.xlsx
Binary files differ
diff --git a/TestFiles/SH121-FmtNumId-11.xlsx b/TestFiles/SH121-FmtNumId-11.xlsx
new file mode 100644
index 0000000..3717ebd
--- /dev/null
+++ b/TestFiles/SH121-FmtNumId-11.xlsx
Binary files differ
diff --git a/TestFiles/SH122-FmtNumId-12.xlsx b/TestFiles/SH122-FmtNumId-12.xlsx
new file mode 100644
index 0000000..bc632c6
--- /dev/null
+++ b/TestFiles/SH122-FmtNumId-12.xlsx
Binary files differ
diff --git a/TestFiles/SH123-FmtNumId-14.xlsx b/TestFiles/SH123-FmtNumId-14.xlsx
new file mode 100644
index 0000000..5b9a9b0
--- /dev/null
+++ b/TestFiles/SH123-FmtNumId-14.xlsx
Binary files differ
diff --git a/TestFiles/SH124-FmtNumId-15.xlsx b/TestFiles/SH124-FmtNumId-15.xlsx
new file mode 100644
index 0000000..5b59fc6
--- /dev/null
+++ b/TestFiles/SH124-FmtNumId-15.xlsx
Binary files differ
diff --git a/TestFiles/SH125-FmtNumId-16.xlsx b/TestFiles/SH125-FmtNumId-16.xlsx
new file mode 100644
index 0000000..895e4e3
--- /dev/null
+++ b/TestFiles/SH125-FmtNumId-16.xlsx
Binary files differ
diff --git a/TestFiles/SH126-FmtNumId-17.xlsx b/TestFiles/SH126-FmtNumId-17.xlsx
new file mode 100644
index 0000000..97913f5
--- /dev/null
+++ b/TestFiles/SH126-FmtNumId-17.xlsx
Binary files differ
diff --git a/TestFiles/SH127-FmtNumId-18.xlsx b/TestFiles/SH127-FmtNumId-18.xlsx
new file mode 100644
index 0000000..a4781ea
--- /dev/null
+++ b/TestFiles/SH127-FmtNumId-18.xlsx
Binary files differ
diff --git a/TestFiles/SH128-FmtNumId-19.xlsx b/TestFiles/SH128-FmtNumId-19.xlsx
new file mode 100644
index 0000000..6da92aa
--- /dev/null
+++ b/TestFiles/SH128-FmtNumId-19.xlsx
Binary files differ
diff --git a/TestFiles/SH129-FmtNumId-20.xlsx b/TestFiles/SH129-FmtNumId-20.xlsx
new file mode 100644
index 0000000..66255d0
--- /dev/null
+++ b/TestFiles/SH129-FmtNumId-20.xlsx
Binary files differ
diff --git a/TestFiles/SH130-FmtNumId-21.xlsx b/TestFiles/SH130-FmtNumId-21.xlsx
new file mode 100644
index 0000000..8df8198
--- /dev/null
+++ b/TestFiles/SH130-FmtNumId-21.xlsx
Binary files differ
diff --git a/TestFiles/SH131-FmtNumId-22.xlsx b/TestFiles/SH131-FmtNumId-22.xlsx
new file mode 100644
index 0000000..8c51aa0
--- /dev/null
+++ b/TestFiles/SH131-FmtNumId-22.xlsx
Binary files differ
diff --git a/TestFiles/SH132-FmtNumId-46.xlsx b/TestFiles/SH132-FmtNumId-46.xlsx
new file mode 100644
index 0000000..dfc3010
--- /dev/null
+++ b/TestFiles/SH132-FmtNumId-46.xlsx
Binary files differ
diff --git a/TestFiles/SH133-FmtNumId-47.xlsx b/TestFiles/SH133-FmtNumId-47.xlsx
new file mode 100644
index 0000000..c449639
--- /dev/null
+++ b/TestFiles/SH133-FmtNumId-47.xlsx
Binary files differ
diff --git a/TestFiles/SH151-Custom-Cell-Format-Currency.xlsx b/TestFiles/SH151-Custom-Cell-Format-Currency.xlsx
new file mode 100644
index 0000000..36d29d4
--- /dev/null
+++ b/TestFiles/SH151-Custom-Cell-Format-Currency.xlsx
Binary files differ
diff --git a/TestFiles/SH152-Custom-Cell-Format.xlsx b/TestFiles/SH152-Custom-Cell-Format.xlsx
new file mode 100644
index 0000000..cddeb57
--- /dev/null
+++ b/TestFiles/SH152-Custom-Cell-Format.xlsx
Binary files differ
diff --git a/TestFiles/SH201-Cell-C1-Without-R-Attr.xlsx b/TestFiles/SH201-Cell-C1-Without-R-Attr.xlsx
new file mode 100644
index 0000000..e26b257
--- /dev/null
+++ b/TestFiles/SH201-Cell-C1-Without-R-Attr.xlsx
Binary files differ
diff --git a/TestFiles/SH202-Cell-C1-D1-Without-R-Attr.xlsx b/TestFiles/SH202-Cell-C1-D1-Without-R-Attr.xlsx
new file mode 100644
index 0000000..2fd2266
--- /dev/null
+++ b/TestFiles/SH202-Cell-C1-D1-Without-R-Attr.xlsx
Binary files differ
diff --git a/TestFiles/SH203-Cell-C1-D1-E1-Without-R-Attr.xlsx b/TestFiles/SH203-Cell-C1-D1-E1-Without-R-Attr.xlsx
new file mode 100644
index 0000000..8409213
--- /dev/null
+++ b/TestFiles/SH203-Cell-C1-D1-E1-Without-R-Attr.xlsx
Binary files differ
diff --git a/TestFiles/SH204-Cell-A1-B1-C1-Without-R-Attr.xlsx b/TestFiles/SH204-Cell-A1-B1-C1-Without-R-Attr.xlsx
new file mode 100644
index 0000000..edc3632
--- /dev/null
+++ b/TestFiles/SH204-Cell-A1-B1-C1-Without-R-Attr.xlsx
Binary files differ
diff --git a/TestFiles/Spreadsheet.xlsx b/TestFiles/Spreadsheet.xlsx
new file mode 100644
index 0000000..8bf78f3
--- /dev/null
+++ b/TestFiles/Spreadsheet.xlsx
Binary files differ
diff --git a/TestFiles/T0010.html b/TestFiles/T0010.html
new file mode 100644
index 0000000..f4f7e7c
--- /dev/null
+++ b/TestFiles/T0010.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+<style type="text/css">
+h1 { font-family: Helvetica; }
+h1 em { color: red; }
+ol { font-size: smaller; }
+ul { font-size: larger; }
+.fzz { font-size: 1ex; }
+.fzz2 { font-size: 120%; }
+.fzz3 { font-size: .5in; }
+.fzz4 { font-size: 2.54cm; }
+.fzz5 { font-size: 254mm; }
+.fzz6 { font-size: 1pc; }
+.fzz7 { font-size: 100px; }
+.bar { text-indent: 2em; }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+ <ol>
+ <li>one</li>
+ <li>two</li>
+ </ol>
+ <ul>
+ <li>one</li>
+ <li>two</li>
+ </ul>
+ <p class='fzz'>bar1</p>
+ <p class='fzz2'>bar2</p>
+ <p class='fzz3'>bar3</p>
+ <p class='fzz4'>bar4</p>
+ <p class='fzz5'>bar5</p>
+ <p class='fzz6'>bar6</p>
+ <p class='fzz7'>bar7</p>
+ <p class='bar'>bar8</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0011.html b/TestFiles/T0011.html
new file mode 100644
index 0000000..cab2b26
--- /dev/null
+++ b/TestFiles/T0011.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <p>Paragraph 1</p>
+ <hr />
+ <p>Paragraph 2</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0012.html b/TestFiles/T0012.html
new file mode 100644
index 0000000..637326b
--- /dev/null
+++ b/TestFiles/T0012.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <p>Paragraph 1 <i>with italic text</i> and some more text.</p>
+ <hr />
+ <p>Paragraph 2</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0013.html b/TestFiles/T0013.html
new file mode 100644
index 0000000..2e8bb44
--- /dev/null
+++ b/TestFiles/T0013.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <p>Paragraph 1 <s>with s text</s> and some more text.</p>
+ <hr />
+ <p>Paragraph 1 <strong>with strong text</strong> and some more text.</p>
+ <hr />
+ <p>Paragraph 1 <sub>with sub text</sub> and some more text.</p>
+ <hr />
+ <p>Paragraph 1 <sup>with sup text</sup> and some more text.</p>
+ <hr />
+ <p>Paragraph 1 <u>with u text</u> and some more text.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0014.html b/TestFiles/T0014.html
new file mode 100644
index 0000000..7b37f48
--- /dev/null
+++ b/TestFiles/T0014.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <table>
+ <tr>
+ <td></td>
+ <td>def</td>
+ <td>ghi</td>
+ </tr>
+ <tr>
+ <td>abc</td>
+ <td>def</td>
+ <td>ghi</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td>ghi</td>
+ </tr>
+ </table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0015.html b/TestFiles/T0015.html
new file mode 100644
index 0000000..de524c2
--- /dev/null
+++ b/TestFiles/T0015.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <p>Paragraph 1</p>
+ <p style="margin-top: 24pt;">Paragraph 2</p>
+ <p>Paragraph 3</p>
+ <p>Paragraph 4</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0020.html b/TestFiles/T0020.html
new file mode 100644
index 0000000..4615421
--- /dev/null
+++ b/TestFiles/T0020.html
@@ -0,0 +1,7 @@
+<html>
+<head/>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0030.html b/TestFiles/T0030.html
new file mode 100644
index 0000000..ced41a9
--- /dev/null
+++ b/TestFiles/T0030.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0040.html b/TestFiles/T0040.html
new file mode 100644
index 0000000..c694f9d
--- /dev/null
+++ b/TestFiles/T0040.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p {
+color: red;
+margin: 3em;
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0050.html b/TestFiles/T0050.html
new file mode 100644
index 0000000..f612031
--- /dev/null
+++ b/TestFiles/T0050.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+p {
+color: red;
+margin: 3em;
+border: thin solid red;
+}
+</style>
+</head>
+<body>
+ <p>This is an html document in a string literal.</p>
+ <h1>Html Heading</h1>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0060.html b/TestFiles/T0060.html
new file mode 100644
index 0000000..3cdc686
--- /dev/null
+++ b/TestFiles/T0060.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+p {
+color: red;
+margin: 3em;
+border: medium solid red;
+}
+</style>
+</head>
+<body>
+ <p>This is an html document in a string literal.</p>
+ <h1>Html Heading</h1>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0070.html b/TestFiles/T0070.html
new file mode 100644
index 0000000..0f3396c
--- /dev/null
+++ b/TestFiles/T0070.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+p {
+color: red;
+margin: 3em;
+border: thick solid red;
+}
+</style>
+</head>
+<body>
+ <p>This is an html document in a string literal.</p>
+ <h1>Html Heading</h1>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0080.html b/TestFiles/T0080.html
new file mode 100644
index 0000000..56627e9
--- /dev/null
+++ b/TestFiles/T0080.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+p {
+color: red;
+margin: 3em;
+border: thin solid red;
+padding: 3em;
+}
+</style>
+</head>
+<body>
+ <p>This is an html document in a string literal.</p>
+ <h1>Html Heading</h1>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0090.html b/TestFiles/T0090.html
new file mode 100644
index 0000000..fd74124
--- /dev/null
+++ b/TestFiles/T0090.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+p {
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p><em style="margin-bottom: 0.3em; float:left; font-size:5em; margin-right: 0.3em">T</em>his is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0100.html b/TestFiles/T0100.html
new file mode 100644
index 0000000..8eeae94
--- /dev/null
+++ b/TestFiles/T0100.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+p {
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an <em style="position: relative; width: 160px; display: block; top: 5em; left: 5em;">html document</em> in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0110.html b/TestFiles/T0110.html
new file mode 100644
index 0000000..501a57a
--- /dev/null
+++ b/TestFiles/T0110.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+<style type="text/css">
+table {
+ border-spacing: 0px;
+}
+td {
+ /* padding: 1em; */
+ border: black thick solid;
+ background: yellow;
+}
+</style>
+</head>
+<body style='font-family: Calibri'>
+<table style='border: thick solid red; font-size: 11pt; border-spacing: .25in; border-collapse: separate;'>
+<tbody>
+<tr>
+<td style='width: 411px; border: black thick dashed;'>aaa</td>
+<td style='padding: 0px;'>bbb</td>
+<td style='padding: 0px;'>ccc</td></tr>
+<tr>
+<td style='width: 411px;padding: 0px;'>ddd</td>
+<td style='padding: 0px;'>eee</td>
+<td style='padding: 0px;'>ggg</td></tr>
+<tr>
+<td style='border: black thick solid; width: 411px;padding: 0px;'>hhh</td>
+<td style='padding: 0px;'>iii</td>
+<td style='border: black thick dashed;padding: 0px;'>jjj</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0111.html b/TestFiles/T0111.html
new file mode 100644
index 0000000..5721ae5
--- /dev/null
+++ b/TestFiles/T0111.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+<style type="text/css">
+table {
+ border-spacing: 0px;
+}
+td {
+ /* padding: 1em; */
+ border: black thick solid;
+ background: yellow;
+}
+</style>
+</head>
+<body style='font-family: Calibri'>
+<table style='border: thick solid red; font-size: 11pt; border-collapse: separate;'>
+<tbody>
+<tr>
+<td style='width: 411px; border: black thick dashed; padding: 0px;'>aaa</td>
+<td style='padding: 0px;'>bbb</td>
+<td style='padding: 0px;'>ccc</td></tr>
+<tr>
+<td style='width: 411px;padding: 0px;'>ddd</td>
+<td style='padding: 0px;'>eee</td>
+<td style='padding: 0px;'>ggg</td></tr>
+<tr>
+<td style='border: black thick solid; width: 411px;padding: 0px;'>hhh</td>
+<td style='padding: 0px;'>iii</td>
+<td style='border: black thick dashed;padding: 0px;'>jjj</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0112.html b/TestFiles/T0112.html
new file mode 100644
index 0000000..22be480
--- /dev/null
+++ b/TestFiles/T0112.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+<style type="text/css">
+td {
+ border: black thick solid;
+ background: yellow;
+}
+</style>
+</head>
+<body style='font-family: Calibri'>
+<table style='border: thick solid red; font-size: 11pt;'>
+<tbody>
+<tr>
+<td style='width: 411px; border: black thick dashed;'>aaa</td>
+<td style=''>bbb</td>
+<td style=''>ccc</td></tr>
+<tr>
+<td style='width: 411px;'>ddd</td>
+<td style=''>eee</td>
+<td style=''>ggg</td></tr>
+<tr>
+<td style='border: black thick solid; width: 411px;'>hhh</td>
+<td style=''>iii</td>
+<td style='border: black thick dashed;'>jjj</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0120.html b/TestFiles/T0120.html
new file mode 100644
index 0000000..84423e4
--- /dev/null
+++ b/TestFiles/T0120.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+h1, p {
+color: green;
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0130.html b/TestFiles/T0130.html
new file mode 100644
index 0000000..9bf9001
--- /dev/null
+++ b/TestFiles/T0130.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+h1 { font-weight: bold }
+h1 { font-size: 12px }
+h1 { line-height: 14px }
+h1 { font-family: Helvetica }
+h1 { font-variant: normal }
+h1 { font-style: normal }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0140.html b/TestFiles/T0140.html
new file mode 100644
index 0000000..b9d0ee2
--- /dev/null
+++ b/TestFiles/T0140.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style type="text/css">
+h1 {
+font-weight: bold;
+font-size: 12px;
+line-height: 14px;
+font-family: Helvetica;
+font-variant: normal;
+font-style: normal
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>This is an html document in a string literal.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0150.html b/TestFiles/T0150.html
new file mode 100644
index 0000000..d505a35
--- /dev/null
+++ b/TestFiles/T0150.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+h1 { margin: 0.5em }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>em margin.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0160.html b/TestFiles/T0160.html
new file mode 100644
index 0000000..7c716af
--- /dev/null
+++ b/TestFiles/T0160.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+h1 { margin: 1ex; font: bold 16pt helvetica; color: black; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>ex margin.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0170.html b/TestFiles/T0170.html
new file mode 100644
index 0000000..3e95940
--- /dev/null
+++ b/TestFiles/T0170.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+body { font-size: 15pt }
+h1 { font-size: 2em }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>h1 font size is specified in terms of the body font size.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0180.html b/TestFiles/T0180.html
new file mode 100644
index 0000000..467d07b
--- /dev/null
+++ b/TestFiles/T0180.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+h1 { line-height: 100px; vertical-align: text-top; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>line height is specified in terms of the size of the h1 font. TODO Note that this doesn't really work
+ in the Word rendering - puts space above, but not below. There really are no options in WordprocessingML to
+ specify line height. I think that the only possible way that this could be implemented would be to specifically
+ calculate the space before and space after. I'm not completely sure that this could be possible. I am pretty
+ sure that this is not worth the effort.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0190.html b/TestFiles/T0190.html
new file mode 100644
index 0000000..b6fd3ed
--- /dev/null
+++ b/TestFiles/T0190.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style type="text/css">
+body {
+ font-size: 24px;
+ text-indent: 3em;
+}
+h1 { font-size: 12px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>body text indent is 3em of 24px. h1 inherits. Its text indent is also the same - it inherits the computed value.
+ However, it is interesting. IE9 recomputes 3em * 12px indent for h1. Word computes 3em * 12px indent for h1, and 3em * 24px for p.
+ Neither word nor IE are correct. My conversion, and chrome are correct.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0200.html b/TestFiles/T0200.html
new file mode 100644
index 0000000..08c91e8
--- /dev/null
+++ b/TestFiles/T0200.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 1in;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0210.html b/TestFiles/T0210.html
new file mode 100644
index 0000000..af0936d
--- /dev/null
+++ b/TestFiles/T0210.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 25.4mm;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0220.html b/TestFiles/T0220.html
new file mode 100644
index 0000000..072dd2d
--- /dev/null
+++ b/TestFiles/T0220.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 2.54cm;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0230.html b/TestFiles/T0230.html
new file mode 100644
index 0000000..bd4a988
--- /dev/null
+++ b/TestFiles/T0230.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 72pt;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0240.html b/TestFiles/T0240.html
new file mode 100644
index 0000000..0f05336
--- /dev/null
+++ b/TestFiles/T0240.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 6pc;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0250.html b/TestFiles/T0250.html
new file mode 100644
index 0000000..310ff17
--- /dev/null
+++ b/TestFiles/T0250.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 96px;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0251.html b/TestFiles/T0251.html
new file mode 100644
index 0000000..bc4f46b
--- /dev/null
+++ b/TestFiles/T0251.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12px;
+text-indent: 96px;
+}
+h1 { font-size: 15px }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="text-indent: -48px;">try various units of measure.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0260.html b/TestFiles/T0260.html
new file mode 100644
index 0000000..ca9e546
--- /dev/null
+++ b/TestFiles/T0260.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-size: 12pt;
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>some text</p>
+ <p style="font-size: 120%">try various units of measure.</p>
+ <p style="word-spacing: .5cm">try various units of measure. Interesting TODO - word-spacing doesn't work in Word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0270.html b/TestFiles/T0270.html
new file mode 100644
index 0000000..ec788e6
--- /dev/null
+++ b/TestFiles/T0270.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+body { background: url(./start.jpg) }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>TODO why doesn't this work?</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0280.html b/TestFiles/T0280.html
new file mode 100644
index 0000000..680944c
--- /dev/null
+++ b/TestFiles/T0280.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+<style type="text/css">
+body {
+counter-reset: chapter;
+}
+h1:before {
+content: "Chapter " counter(chapter) ". ";
+counter-increment: chapter;
+}
+h1 {
+counter-reset: section;
+}
+h2:before {
+content: counter(chapter) "." counter(section) " ";
+counter-increment: section;
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>H2</h2>
+<p>bar</p>
+<h2>hhh2</h2>
+<p>baz</p>
+<h1>zzz</h1>
+<p>111</p>
+<p>TODO why doesn't this work?</p>
+<h2>uuu</h2>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0290.html b/TestFiles/T0290.html
new file mode 100644
index 0000000..82bda7d
--- /dev/null
+++ b/TestFiles/T0290.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+<style type="text/css">
+body {
+font-family: Calibri;
+}
+</style>
+</head>
+<body>
+<p>test paragraph</p>
+<ol>
+<li>item a</li>
+<li>item b</li>
+<ol>
+<li>item c</li>
+<li>item d</li>
+<li>item e</li>
+<ol>
+<li>item f</li>
+</ol>
+<ol>
+<li>item g</li>
+</ol>
+<li>item h</li>
+</ol>
+<li>item i</li>
+<li>item j</li>
+</ol>
+<ol>
+<li>item k</li>
+<li>item l</li>
+</ol>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0300.html b/TestFiles/T0300.html
new file mode 100644
index 0000000..cf2c7e0
--- /dev/null
+++ b/TestFiles/T0300.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<ol style="list-style-type: lower-alpha">
+<li>item a</li>
+<li>item b</li>
+<ol>
+<li>item c</li>
+<li>item d</li>
+<li>item e</li>
+<ol>
+<li>item f</li>
+</ol>
+<ol>
+<li>item g</li>
+</ol>
+<li>item h</li>
+</ol>
+<li>item i</li>
+<li>item j</li>
+</ol>
+<ol>
+<li>item k</li>
+<li>item l</li>
+</ol>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0310.html b/TestFiles/T0310.html
new file mode 100644
index 0000000..cd80335
--- /dev/null
+++ b/TestFiles/T0310.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<ol>
+<li>aaa</li>
+<li>bbb</li>
+<li>ccc</li>
+</ol>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0320.html b/TestFiles/T0320.html
new file mode 100644
index 0000000..d16c20f
--- /dev/null
+++ b/TestFiles/T0320.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<ol>
+<li>item a</li>
+<li>item b</li>
+<ol style="list-style-type: lower-alpha">
+<li>item c</li>
+<li>item d</li>
+<li>item e</li>
+<ol>
+<li>item f</li>
+</ol>
+<ol>
+<li>item g</li>
+</ol>
+<li>item h</li>
+</ol>
+<li>item i</li>
+<li>item j</li>
+</ol>
+<ol>
+<li>item k</li>
+<li>item l</li>
+</ol>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0330.html b/TestFiles/T0330.html
new file mode 100644
index 0000000..6ec0be9
--- /dev/null
+++ b/TestFiles/T0330.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<ol style="list-style-type: upper-alpha">
+<li>item a</li>
+<li>item b</li>
+<ol style="list-style-type: lower-alpha">
+<li>item c</li>
+<ol style="list-style-type: lower-roman">
+<li>item c</li>
+<li>item d</li>
+<li>item e</li>
+</ol>
+<li>item d</li>
+<li>item e</li>
+<ol>
+<li>item f</li>
+</ol>
+<ol>
+<li>item g</li>
+</ol>
+<li>item h</li>
+</ol>
+<li>item i</li>
+<li>item j</li>
+</ol>
+<ol style="list-style-type: decimal-leading-zero">
+<li>item k</li>
+<li>item l</li>
+</ol>
+<ol style="list-style-type: upper-roman">
+<li>item k</li>
+<ol style="list-style-type: lower-roman">
+<li>item k</li>
+<li>item l</li>
+</ol>
+<li>item l</li>
+</ol>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0340.html b/TestFiles/T0340.html
new file mode 100644
index 0000000..3f80bd6
--- /dev/null
+++ b/TestFiles/T0340.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style type="text/css">
+p {counter-increment: par-num}
+h1 {counter-reset: par-num}
+p:before {content: counter(par-num, upper-roman) ". "
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h1>Html Heading</h1>
+ <p>todo this doesn't work.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0350.html b/TestFiles/T0350.html
new file mode 100644
index 0000000..0602c81
--- /dev/null
+++ b/TestFiles/T0350.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+p { color: green }
+h1 { color: aqua }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0360.html b/TestFiles/T0360.html
new file mode 100644
index 0000000..7d97f38
--- /dev/null
+++ b/TestFiles/T0360.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: #f00; }
+p { color: #0f0; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0370.html b/TestFiles/T0370.html
new file mode 100644
index 0000000..78390dc
--- /dev/null
+++ b/TestFiles/T0370.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: #ff0000; }
+p { color: #0000ff; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0380.html b/TestFiles/T0380.html
new file mode 100644
index 0000000..c014dac
--- /dev/null
+++ b/TestFiles/T0380.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: rgb(255, 255, 0); }
+p { color: rgb(0, 255, 255); }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0390.html b/TestFiles/T0390.html
new file mode 100644
index 0000000..b9f7546
--- /dev/null
+++ b/TestFiles/T0390.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: rgb(100%, 100%, 0%); }
+p { color: rgb(0%, 50%, 50%); }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0400.html b/TestFiles/T0400.html
new file mode 100644
index 0000000..fe9b74d
--- /dev/null
+++ b/TestFiles/T0400.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+* { color: red; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz TODO doesn't work for word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0410.html b/TestFiles/T0410.html
new file mode 100644
index 0000000..94cebcc
--- /dev/null
+++ b/TestFiles/T0410.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+p { color: red; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0420.html b/TestFiles/T0420.html
new file mode 100644
index 0000000..170271d
--- /dev/null
+++ b/TestFiles/T0420.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+h1 > p { color: red; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz weird todo this doesn't work for either IE or Word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0430.html b/TestFiles/T0430.html
new file mode 100644
index 0000000..ba9ad54
--- /dev/null
+++ b/TestFiles/T0430.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+a:link { color: red; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz<a href='http://www.ericwhite.com'>Eric White</a></p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0431.html b/TestFiles/T0431.html
new file mode 100644
index 0000000..1cdcf25
--- /dev/null
+++ b/TestFiles/T0431.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+a:link { color: red; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz<a href='#'>Eric White</a></p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0432.html b/TestFiles/T0432.html
new file mode 100644
index 0000000..c918bad
--- /dev/null
+++ b/TestFiles/T0432.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+a:link { color: red; }
+</style>
+</head>
+<body>
+ <table><tr><td><a href="http://www.example.com">example</a></td></tr></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0440.html b/TestFiles/T0440.html
new file mode 100644
index 0000000..0298fbc
--- /dev/null
+++ b/TestFiles/T0440.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+a { color: red; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz<a href='http://www.ericwhite.com'>Eric White</a></p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0450.html b/TestFiles/T0450.html
new file mode 100644
index 0000000..c930a49
--- /dev/null
+++ b/TestFiles/T0450.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz<a href='http://www.ericwhite.com'>Eric White</a></p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0460.html b/TestFiles/T0460.html
new file mode 100644
index 0000000..36ac4f5
--- /dev/null
+++ b/TestFiles/T0460.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+a:link { color: red }
+a:hover { color: blue }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz<a href='http://www.ericwhite.com'>Eric White</a></p>
+ <h2>Html Heading</h2>
+ <p>fzz TODO hover doesnt work for word, but didnt expect it to.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0470.html b/TestFiles/T0470.html
new file mode 100644
index 0000000..b44688b
--- /dev/null
+++ b/TestFiles/T0470.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+h1 em { color: red }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0480.html b/TestFiles/T0480.html
new file mode 100644
index 0000000..46378ce
--- /dev/null
+++ b/TestFiles/T0480.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+p:lang(fr) { color: red }
+p:lang(en) { color: green }
+p:lang(en-us) { color: yellow }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p lang='fr'>fzz</p>
+ <h2>Html Heading</h2>
+ <p lang='en-us'>fzz</p>
+ <p lang='en'>fzz todo doesnt work</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0490.html b/TestFiles/T0490.html
new file mode 100644
index 0000000..0ae0228
--- /dev/null
+++ b/TestFiles/T0490.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+h1 + p { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz todo doesnt work</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0500.html b/TestFiles/T0500.html
new file mode 100644
index 0000000..4ac9fb5
--- /dev/null
+++ b/TestFiles/T0500.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+p[id] { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p ID="xx">fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz todo doesnt work</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0510.html b/TestFiles/T0510.html
new file mode 100644
index 0000000..28ff034
--- /dev/null
+++ b/TestFiles/T0510.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p[lang|="en"] { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p lang='fr'>fzz</p>
+ <h2>Html Heading</h2>
+ <p lang='en-us'>fzz</p>
+ <p lang='en'>fzz todo doesnt work</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0520.html b/TestFiles/T0520.html
new file mode 100644
index 0000000..9b62c87
--- /dev/null
+++ b/TestFiles/T0520.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p[class~="abc"] { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p class="abc">fzz</p>
+ <p>fzz todo doesnt work, although following example does work.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0530.html b/TestFiles/T0530.html
new file mode 100644
index 0000000..7a36148
--- /dev/null
+++ b/TestFiles/T0530.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p.abc { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p class="abc">fzz</p>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0540.html b/TestFiles/T0540.html
new file mode 100644
index 0000000..b1aac58
--- /dev/null
+++ b/TestFiles/T0540.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p#abc { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p id="abc">fzz</p>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0550.html b/TestFiles/T0550.html
new file mode 100644
index 0000000..ccca7db
--- /dev/null
+++ b/TestFiles/T0550.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p, h1 { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0560.html b/TestFiles/T0560.html
new file mode 100644
index 0000000..3708cd4
--- /dev/null
+++ b/TestFiles/T0560.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+*[lang=en] { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p lang="fr">fzz</p>
+ <h2>Html Heading</h2>
+ <p lang="en-us">fzz</p>
+ <p lang="en">Doesn't work for word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0570.html b/TestFiles/T0570.html
new file mode 100644
index 0000000..f8a8874
--- /dev/null
+++ b/TestFiles/T0570.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+[lang=en] { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p lang="fr">fzz</p>
+ <h2>Html Heading</h2>
+ <p lang="en-us">fzz</p>
+ <p lang="en">fzz todo doesnt work</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0580.html b/TestFiles/T0580.html
new file mode 100644
index 0000000..4f07c34
--- /dev/null
+++ b/TestFiles/T0580.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: red }
+em { color: red }
+h1 em { color: blue }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz <em>bar</em></p>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0590.html b/TestFiles/T0590.html
new file mode 100644
index 0000000..79ffee8
--- /dev/null
+++ b/TestFiles/T0590.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+p[id="xx"] { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p id="xx">fzz</p>
+ <h2>Html Heading</h2>
+ <p>doesn't work for word or ie. Works for me and chrome.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0600.html b/TestFiles/T0600.html
new file mode 100644
index 0000000..99db8c1
--- /dev/null
+++ b/TestFiles/T0600.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style type="text/css">
+div * p { color: red; }
+</style>
+</head>
+<body>
+ <div>
+ <div>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ </div>
+ </div>
+ <h2>Html Heading</h2>
+ <p>works in ie and for me, not in word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0610.html b/TestFiles/T0610.html
new file mode 100644
index 0000000..8b5a02f
--- /dev/null
+++ b/TestFiles/T0610.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+p.abc { color: blue }
+p.abc.def { color: red }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p class="abc">fzz</p>
+ <p class="abc def">fzz</p>
+ <p>Here is an example that shows that the CSS processor in Word is really pretty crappy.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0620.html b/TestFiles/T0620.html
new file mode 100644
index 0000000..57eda20
--- /dev/null
+++ b/TestFiles/T0620.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+*#abc { color: blue }
+</style>
+</head>
+<body>
+<h1>Html Heading</h1>
+<p>fzz</p>
+<h2>Html Heading</h2>
+<p ID="abc">fzz</p>
+<p>works in ie, not in word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0622.html b/TestFiles/T0622.html
new file mode 100644
index 0000000..639413b
--- /dev/null
+++ b/TestFiles/T0622.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+*#abc { color: blue }
+</style>
+</head>
+<body>
+<p ID="abc">fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0630.html b/TestFiles/T0630.html
new file mode 100644
index 0000000..1b62d8c
--- /dev/null
+++ b/TestFiles/T0630.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+#abc { color: blue }
+</style>
+</head>
+<body>
+<h1>Html Heading</h1>
+<p>fzz</p>
+<h2 ID="abc">Html Heading</h2>
+<p ID="abc">fzz</p>
+<p>works both in ie and word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0640.html b/TestFiles/T0640.html
new file mode 100644
index 0000000..46cf40d
--- /dev/null
+++ b/TestFiles/T0640.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p#abc { color: blue }
+</style>
+</head>
+<body>
+<h1>Html Heading</h1>
+<p>fzz</p>
+<h2 ID="abc">Html Heading</h2>
+<p ID="abc">fzz</p>
+<p>works both in ie and word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0650.html b/TestFiles/T0650.html
new file mode 100644
index 0000000..a413b7a
--- /dev/null
+++ b/TestFiles/T0650.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+p[ID=abc] { color: red }
+</style>
+</head>
+<body>
+<h1>Html Heading</h1>
+<p>fzz</p>
+<h2 ID="abc">Html Heading</h2>
+<p ID="abc">fzz</p>
+<p>works in chrome, not in ie and word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0651.html b/TestFiles/T0651.html
new file mode 100644
index 0000000..cacbadd
--- /dev/null
+++ b/TestFiles/T0651.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+p[ID=abc] { color: red }
+</style>
+</head>
+<body>
+<p ID="abc">fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0660.html b/TestFiles/T0660.html
new file mode 100644
index 0000000..b764968
--- /dev/null
+++ b/TestFiles/T0660.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+h1:first-child { color: red }
+</style>
+</head>
+<body>
+<h1>H1</h1>
+<p>fzz</p>
+<h1>H1</h1>
+<p>todo works in chrome, not in IE or Word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0670.html b/TestFiles/T0670.html
new file mode 100644
index 0000000..68991ef
--- /dev/null
+++ b/TestFiles/T0670.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+div p:first-child { color: red }
+</style>
+</head>
+<body>
+<h1>H1</h1>
+<div>
+ <p>fzz</p>
+ <p>fzz</p>
+</div>
+<h1>H1</h1>
+<p>todo works in chrome, not in IE or Word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0680.html b/TestFiles/T0680.html
new file mode 100644
index 0000000..37f1766
--- /dev/null
+++ b/TestFiles/T0680.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+p:first-child em { color: red }
+</style>
+</head>
+<body>
+<h1>H1</h1>
+<div>
+ <p>fzz <em>em</em></p>
+ <p>fzz <em>em</em></p>
+</div>
+<h1>H1</h1>
+<p>todo as usual works in chrome, not in IE or Word.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0690.html b/TestFiles/T0690.html
new file mode 100644
index 0000000..dff2199
--- /dev/null
+++ b/TestFiles/T0690.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+h1 { text-transform: uppercase }
+</style>
+</head>
+<body>
+<h1>heading 1 should be uppercase</h1>
+<div>
+ <p>fzz</p>
+ <p>fzz</p>
+</div>
+<h1>h1</h1>
+<p>works in IE and Word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0691.html b/TestFiles/T0691.html
new file mode 100644
index 0000000..0090cbe
--- /dev/null
+++ b/TestFiles/T0691.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+h1 { text-transform: lowercase }
+</style>
+</head>
+<body>
+<h1>HEADING 1 SHOULD BE LOWER CASE</h1>
+<div>
+ <p>fzz</p>
+ <p>fzz</p>
+</div>
+<h1>HEADING 1</h1>
+<p>works in IE and Word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0692.html b/TestFiles/T0692.html
new file mode 100644
index 0000000..50a8a4a
--- /dev/null
+++ b/TestFiles/T0692.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+h1 { text-transform: capitalize }
+</style>
+</head>
+<body>
+<h1>HEADING 1 SHOULD BE CAPITALIZED</h1>
+<div>
+ <p>fzz</p>
+ <p>fzz</p>
+</div>
+<h1>HEADING 1</h1>
+<p>works in IE and Word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0700.html b/TestFiles/T0700.html
new file mode 100644
index 0000000..b6d0160
--- /dev/null
+++ b/TestFiles/T0700.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style type="text/css">
+p:first-line { text-transform: uppercase }
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+</div>
+<h1>h1</h1>
+<p>todo works in IE and chrome, not in Word. may not make sense to support this one.
+ would require writing a layout engine.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0710.html b/TestFiles/T0710.html
new file mode 100644
index 0000000..e7bb8c7
--- /dev/null
+++ b/TestFiles/T0710.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+p:first-letter { text-transform: uppercase; font-size: 24; }
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p>fzz</p>
+ <p>fzz</p>
+</div>
+<h1>h1</h1>
+<p>works in IE, not in Word. this could be easier to implement than first-line.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0720.html b/TestFiles/T0720.html
new file mode 100644
index 0000000..b6d0160
--- /dev/null
+++ b/TestFiles/T0720.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style type="text/css">
+p:first-line { text-transform: uppercase }
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+</div>
+<h1>h1</h1>
+<p>todo works in IE and chrome, not in Word. may not make sense to support this one.
+ would require writing a layout engine.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0730.html b/TestFiles/T0730.html
new file mode 100644
index 0000000..065fff5
--- /dev/null
+++ b/TestFiles/T0730.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+p { font-size: 12pt; line-height: 1.2 }
+p:first-letter { font-size: 200%; font-style: italic;
+ font-weight: bold; float: left; }
+span { text-transform: uppercase }
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+</div>
+<h1>h1</h1>
+<p>todo works in IE and chrome, not in Word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0740.html b/TestFiles/T0740.html
new file mode 100644
index 0000000..b48c66f
--- /dev/null
+++ b/TestFiles/T0740.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+p { font-size: 12pt; }
+span { font-size: 200%; font-style: italic;
+ font-weight: bold; float: left; }
+span { text-transform: uppercase }
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p><span>L</span>orem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+</div>
+<h1>h1</h1>
+<p>todo works in IE and chrome, partly in Word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0742.html b/TestFiles/T0742.html
new file mode 100644
index 0000000..4b068ee
--- /dev/null
+++ b/TestFiles/T0742.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<style type="text/css">
+p { font-size: 12pt; font-weight: bold; }
+</style>
+</head>
+<body>
+<p style='font-weight: normal'>font-weight: normal</p>
+<p style='font-weight: bold'>font-weight: bold</p>
+<p style='font-weight: bolder'>font-weight: bolder</p>
+<p style='font-weight: lighter'>font-weight: lighter</p>
+<p style='font-weight: 100'>font-weight: 100</p>
+<p style='font-weight: 200'>font-weight: 200</p>
+<p style='font-weight: 300'>font-weight: 300</p>
+<p style='font-weight: 400'>font-weight: 400</p>
+<p style='font-weight: 500'>font-weight: 500</p>
+<p style='font-weight: 600'>font-weight: 600</p>
+<p style='font-weight: 700'>font-weight: 700</p>
+<p style='font-weight: 800'>font-weight: 800</p>
+<p style='font-weight: 900'>font-weight: 900</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0745.html b/TestFiles/T0745.html
new file mode 100644
index 0000000..eefcbec
--- /dev/null
+++ b/TestFiles/T0745.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+p { font-size: 12pt; }
+span { font-size: 200%; font-style: italic;
+ font-weight: bold; float: left; }
+span { text-transform: uppercase }
+</style>
+</head>
+<body>
+<p><span>L</span>orem ipsum.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0750.html b/TestFiles/T0750.html
new file mode 100644
index 0000000..f8a54e3
--- /dev/null
+++ b/TestFiles/T0750.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+p { font-size: 12pt; line-height: 1.2 }
+p:first-letter { font-size: 200%; font-style: italic;
+ font-weight: bold; float: left; }
+span { text-transform: uppercase }
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p>"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+</div>
+<h1>h1</h1>
+<p>todo works in IE and chrome, not in Word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0760.html b/TestFiles/T0760.html
new file mode 100644
index 0000000..b0263f4
--- /dev/null
+++ b/TestFiles/T0760.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+p.special:before {content: "Special! "}
+</style>
+</head>
+<body>
+<h1>h1</h1>
+<div>
+ <p>fzz</p>
+ <p class="special">fzz</p>
+</div>
+<h1>h1</h1>
+<p>works in chrome, not in IE or word</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0770.html b/TestFiles/T0770.html
new file mode 100644
index 0000000..041a184
--- /dev/null
+++ b/TestFiles/T0770.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: blue; }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>em inherits color from h1</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0780.html b/TestFiles/T0780.html
new file mode 100644
index 0000000..dad1a32
--- /dev/null
+++ b/TestFiles/T0780.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+body { font-size: 10pt }
+h1 { font-size: 130% }
+h2 { font-size: 115% }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>h1 becomes 13pt. em inherits computed value.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0790.html b/TestFiles/T0790.html
new file mode 100644
index 0000000..692a759
--- /dev/null
+++ b/TestFiles/T0790.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+h1 { color: red !important }
+h1 { color: blue }
+</style>
+</head>
+<body>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>word does honor important.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0791.html b/TestFiles/T0791.html
new file mode 100644
index 0000000..a5fab53
--- /dev/null
+++ b/TestFiles/T0791.html
@@ -0,0 +1,29 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+ p.classA {
+ color: white;
+ background: blue;
+ margin: 40px 40px 40px 40px;
+ padding: 12px 0px 12px 12px;
+ }
+ p.classB {
+ color: white;
+ background: blue;
+ margin: 40px 40px 40px 40px;
+ padding: 12px 0px 12px 12px;
+ border-style: dashed;
+ border-width: medium;
+ border-color: lime;
+ }
+</style>
+</HEAD>
+<BODY>
+<p>before</p>
+<p class='classA'>first paragraph</p>
+<p class='classB'>second paragraph.</p>
+<p>after</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0792.html b/TestFiles/T0792.html
new file mode 100644
index 0000000..dba4f8e
--- /dev/null
+++ b/TestFiles/T0792.html
@@ -0,0 +1,29 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+ p.classA {
+ color: white;
+ background: blue;
+ margin: 0px 0px 0px 0px;
+ padding: 12px 12px 12px 12px;
+ }
+ p.classB {
+ color: white;
+ background: blue;
+ margin: 0px 0px 0px 0px;
+ padding: 12px 12px 12px 12px;
+ border-style: dashed;
+ border-width: medium;
+ border-color: lime;
+ }
+</style>
+</HEAD>
+<BODY>
+<p>before</p>
+<p class='classA'>first paragraph</p>
+<p class='classB'>second paragraph.</p>
+<p>after</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0793.html b/TestFiles/T0793.html
new file mode 100644
index 0000000..59b1c07
--- /dev/null
+++ b/TestFiles/T0793.html
@@ -0,0 +1,29 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+ p.classA {
+ color: white;
+ background: blue;
+ margin: 0px 0px 0px 0px;
+ padding: 24px 24px 24px 24px;
+ }
+ p.classB {
+ color: white;
+ background: blue;
+ margin: 0px 0px 0px 0px;
+ padding: 24px 24px 24px 24px;
+ border-style: dashed;
+ border-width: medium;
+ border-color: lime;
+ }
+</style>
+</HEAD>
+<BODY>
+<p>before</p>
+<p class='classA'>first paragraph</p>
+<p class='classB'>second paragraph.</p>
+<p>after</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0794.html b/TestFiles/T0794.html
new file mode 100644
index 0000000..779dd4b
--- /dev/null
+++ b/TestFiles/T0794.html
@@ -0,0 +1,29 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+ p.classA {
+ color: white;
+ background: blue;
+ margin: 6pt 6pt 6pt 6pt;
+ padding: 24px 24px 24px 24px;
+ }
+ p.classB {
+ color: white;
+ background: blue;
+ margin: 6pt 6pt 6pt 6pt;
+ padding: 24px 24px 24px 24px;
+ border-style: dashed;
+ border-width: medium;
+ border-color: lime;
+ }
+</style>
+</HEAD>
+<BODY>
+<p>before</p>
+<p class='classA'>first paragraph</p>
+<p class='classB'>second paragraph.</p>
+<p>after</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0795.html b/TestFiles/T0795.html
new file mode 100644
index 0000000..63fecf3
--- /dev/null
+++ b/TestFiles/T0795.html
@@ -0,0 +1,34 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+ p.classA {
+ color: white;
+ background: blue;
+ margin: 18px 18px 18px 18px;
+ padding: 10pt 10pt 10pt 10pt;
+ }
+ p.classB {
+ color: white;
+ background: blue;
+ margin: 18px 18px 18px 18px;
+ padding: 10pt 10pt 10pt 10pt;
+ border-style: dashed;
+ border-width: medium;
+ border-color: lime;
+ }
+</style>
+</HEAD>
+<BODY>
+<p>before</p>
+<p class='classA'>first paragraph</p>
+<p class='classB'>second paragraph.</p>
+<p class='classB'>third paragraph.</p>
+<p class='classB'>fourth paragraph.</p>
+<p class='classA'>fifth paragraph</p>
+<p class='classA'>sixth paragraph</p>
+<p class='classA'>seventh paragraph</p>
+<p>after</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0802.html b/TestFiles/T0802.html
new file mode 100644
index 0000000..826e988
--- /dev/null
+++ b/TestFiles/T0802.html
@@ -0,0 +1,17 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+</style>
+</HEAD>
+<BODY>
+<p>-----</p>
+<p style='margin:12pt'>12pt</p>
+<p>-----</p>
+<p style='margin:20pt'>20pt</p>
+<p>-----</p>
+<p style='margin:30pt'>30pt</p>
+<p>-----</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0804.html b/TestFiles/T0804.html
new file mode 100644
index 0000000..a5fe2c2
--- /dev/null
+++ b/TestFiles/T0804.html
@@ -0,0 +1,17 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+</style>
+</HEAD>
+<BODY>
+<p>-----</p>
+<h1 style='margin:12pt'>12pt</h1>
+<p>-----</p>
+<h2 style='margin:20pt'>20pt</h2>
+<p>-----</p>
+<h3 style='margin:30pt'>30pt</h3>
+<p>-----</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0805.html b/TestFiles/T0805.html
new file mode 100644
index 0000000..7be6203
--- /dev/null
+++ b/TestFiles/T0805.html
@@ -0,0 +1,17 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+</style>
+</HEAD>
+<BODY>
+<p>-----</p>
+<ul>
+<li style='margin:12pt'>12pt</li>
+<li style='margin:20pt'>20pt</li>
+<li style='margin:30pt'>30pt</li>
+</ul>
+<p>-----</p>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0810.html b/TestFiles/T0810.html
new file mode 100644
index 0000000..cbbdd16
--- /dev/null
+++ b/TestFiles/T0810.html
@@ -0,0 +1,32 @@
+
+<HTML>
+<HEAD>
+<TITLE>Examples of margins, padding, and borders</TITLE>
+<style type='text/css'>
+ UL {
+ background: yellow;
+ margin: 12px 12px 12px 12px;
+ padding: 3px 3px 3px 3px;
+ }
+ LI {
+ color: white;
+ background: blue;
+ margin: 12px 12px 12px 12px;
+ padding: 12px 0px 12px 12px;
+ list-style: none;
+ }
+ LI.withborder {
+ border-style: dashed;
+ border-width: thin;
+ border-color: lime;
+ }
+</style>
+</HEAD>
+<BODY>
+<UL>
+ <li>First element of list</li>
+ <li class='withborder'>Second element of list is a bit longer to illustrate wrapping.</li>
+</UL>
+<P>todo this doesn't work properly in Word - the list-style: none is not working. also margins dont work properly.</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0812.html b/TestFiles/T0812.html
new file mode 100644
index 0000000..e94aca9
--- /dev/null
+++ b/TestFiles/T0812.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<title>Examples of margins, padding, and borders</title>
+<style type='text/css'>
+</style>
+</head>
+<body>
+<p style='font: xx-small Calibri;'>font: xx-small Calibri</p>
+<p style='font: x-small Calibri;'>font: x-small Calibri</p>
+<p style='font: small Calibri;'>font: small Calibri</p>
+<p style='font: medium Calibri;'>font: medium Calibri</p>
+<p style='font: large Calibri;'>font: large Calibri</p>
+<p style='font: x-large Calibri;'>font: x-large Calibri</p>
+<p style='font: xx-large Calibri;'>font: xx-large Calibri</p>
+<p style='font: xx-small "times new roman";'>font: xx-small Times New Roman</p>
+<p style='font: x-small "times new roman";'>font: x-small Times New Roman</p>
+<p style='font: small "times new roman";'>font: small Times New Roman</p>
+<p style='font: medium "times new roman";'>font: medium Times New Roman</p>
+<p style='font: large "times new roman";'>font: large Times New Roman</p>
+<p style='font: x-large "times new roman";'>font: x-large Times New Roman</p>
+<p style='font: xx-large "times new roman";'>font: xx-large Times New Roman</p>
+<p style='font: xx-small "times";'>font: xx-small times</p>
+<p style='font: x-small "times";'>font: x-small times</p>
+<p style='font: small "times";'>font: small times</p>
+<p style='font: medium "times";'>font: medium times</p>
+<p style='font: large "times";'>font: large times</p>
+<p style='font: x-large "times";'>font: x-large times</p>
+<p style='font: xx-large "times";'>font: xx-large times</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0814.html b/TestFiles/T0814.html
new file mode 100644
index 0000000..32ad402
--- /dev/null
+++ b/TestFiles/T0814.html
@@ -0,0 +1,14 @@
+
+<html>
+<head>
+<title>Examples of margins, padding, and borders</title>
+<style type='text/css'>
+</style>
+</head>
+<body>
+<p style='font-family: Calibri, san-serif;'>Should be Calibri</p>
+<p style='font-family: fzz, Calibri, san-serif;'>Should be Calibri (bypassing fzz)</p>
+<p style='font-family: fzz, serif;'>Should be Times New Roman (bypassing fzz, mapping serif to times)</p>
+<p style='font-family: sans-serif;'>Should be Arial</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0820.html b/TestFiles/T0820.html
new file mode 100644
index 0000000..bf09eaa
--- /dev/null
+++ b/TestFiles/T0820.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<style type="text/css">
+body {
+background: orange;
+}
+div {
+background: yellow;
+margin: 0% 0% 0% 0%;
+border: thin solid red;
+padding: 0% 0% 0% 0%;
+}
+p {
+background: red;
+margin: 3% 3% 3% 3%;
+padding: 3% 3% 3% 3%;
+border: thin solid blue;
+}
+h1 {
+background: green;
+margin: 3% 3% 3% 3%;
+padding: 3% 3% 3% 3%;
+border: thin solid blue;
+}
+</style>
+</head>
+<body>
+ <div>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ </div>
+ <h2>Html Heading</h2>
+ <p>this demonstrates setting borders, margins by percentage.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0821.html b/TestFiles/T0821.html
new file mode 100644
index 0000000..f01dd3e
--- /dev/null
+++ b/TestFiles/T0821.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<style type="text/css">
+p {
+background: red;
+margin: 3% 3% 3% 3%;
+/* padding: 3% 3% 3% 3%; */
+/*margin: 10px 10px 10px 10px;
+padding: 10px 10px 10px 10px;*/
+border: thin solid blue;
+}
+</style>
+</head>
+<body>
+ <div>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ </div>
+ <h2>Html Heading</h2>
+ <p>this demonstrates setting borders, margins by percentage.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0830.html b/TestFiles/T0830.html
new file mode 100644
index 0000000..b847225
--- /dev/null
+++ b/TestFiles/T0830.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<style type="text/css">
+body {
+background: orange;
+}
+div {
+background: yellow;
+margin: 0% 0% 0% 0%;
+border: thin solid red;
+padding: 0% 0% 0% 0%;
+}
+p {
+background: red;
+margin: 12px 12px 12px 12px;
+padding: 4px 4px 4px 4px;
+border: thin solid blue;
+}
+h1 {
+background: green;
+margin: 12px 12px 12px 12px;
+padding: 4px 4px 4px 4px;
+border: thin solid blue;
+}
+</style>
+</head>
+<body>
+ <div>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ </div>
+ <h2>Html Heading</h2>
+ <p>demonstrates borders, margins by fixed size.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0840.html b/TestFiles/T0840.html
new file mode 100644
index 0000000..37125c2
--- /dev/null
+++ b/TestFiles/T0840.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+<style type="text/css">
+body {
+background: orange;
+}
+div {
+background: yellow;
+margin: 0% 0% 0% 0%;
+border: thin solid red;
+padding: 12px 12px 12px 12px;
+}
+p {
+background: red;
+margin: 4px 4px 4px 4px;
+padding: 2px 2px 2px 2px;
+border: thin solid blue;
+}
+h1 {
+background: green;
+margin: 4px 4px 16px 4px;
+padding: 2px 2px 2px 2px;
+border: thin solid blue;
+}
+</style>
+</head>
+<body>
+ <div>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>fzz</p>
+ <p>bar</p>
+ <p>baz</p>
+ </div>
+ <h2>Html Heading</h2>
+ <p>demonstrates borders, margins by fixed size.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0850.html b/TestFiles/T0850.html
new file mode 100644
index 0000000..e3e4f1c
--- /dev/null
+++ b/TestFiles/T0850.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <div>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. <div display='inline-block'><p>Maecenas porttitor congue massa.</p><p>fzz</p><p>bar</p></div> Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>bar</p>
+ <p>baz</p>
+ </div>
+ <h2>Html Heading</h2>
+ <p>demonstrates borders, margins by fixed size.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0851.html b/TestFiles/T0851.html
new file mode 100644
index 0000000..888ce77
--- /dev/null
+++ b/TestFiles/T0851.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <div>
+ <p>abc <div display='inline-block'><p>def</p></div></p>
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0860.html b/TestFiles/T0860.html
new file mode 100644
index 0000000..31e2724
--- /dev/null
+++ b/TestFiles/T0860.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <div>
+ <h1>Html <em>Head</em>ing</h1>
+ <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna. Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci. Aenean nec lorem. In porttitor. Donec laoreet nonummy augue. Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy. Fusce aliquet pede non pede. Suspendisse dapibus lorem pellentesque magna. Integer nulla. Donec blandit feugiat ligula. Donec hendrerit, felis et imperdiet euismod, purus ipsum pretium metus, in lacinia nulla nisl eget sapien.</p>
+ <p>bar</p>
+ <p>baz</p>
+ <p style='position:fixed; bottom: 10px; right: 10px; background: red; border: thin solid blue; '>Test</p>
+ </div>
+ <h2>Html Heading</h2>
+ <p>demonstrates borders, margins by fixed size.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0870.html b/TestFiles/T0870.html
new file mode 100644
index 0000000..06e8ec4
--- /dev/null
+++ b/TestFiles/T0870.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>anon text interrupted by a block</title>
+<style>
+p { display: inline; border: thin solid blue; }
+span { display: block }
+</style>
+</head>
+<body>
+<p>
+this is anonymous text before the span. <span>This is the content of SPAN.</span> this is anonymous text after the span.
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0880.html b/TestFiles/T0880.html
new file mode 100644
index 0000000..a02b614
--- /dev/null
+++ b/TestFiles/T0880.html
@@ -0,0 +1,9 @@
+<html>
+<head/>
+<body>
+ <h1>Html Heading</h1>
+ <p style='position: relative; top:200px; left:200px; border:thin solid blue;'>this is positioned relative</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0890.html b/TestFiles/T0890.html
new file mode 100644
index 0000000..a0dadcc
--- /dev/null
+++ b/TestFiles/T0890.html
@@ -0,0 +1,9 @@
+<html>
+<head/>
+<body>
+ <h1>Html Heading</h1>
+ <p style='position: absolute; top:0px; left:0px; border:thin solid blue;'>This is positioned absolute. It is taken out of the normal flow.</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0900.html b/TestFiles/T0900.html
new file mode 100644
index 0000000..59e5d54
--- /dev/null
+++ b/TestFiles/T0900.html
@@ -0,0 +1,38 @@
+<html>
+<head/>
+<body>
+ <h1>Html Heading</h1>
+ <p style='background: yellow; border: thin solid blue;'>This is a test.</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0910.html b/TestFiles/T0910.html
new file mode 100644
index 0000000..59e5d54
--- /dev/null
+++ b/TestFiles/T0910.html
@@ -0,0 +1,38 @@
+<html>
+<head/>
+<body>
+ <h1>Html Heading</h1>
+ <p style='background: yellow; border: thin solid blue;'>This is a test.</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0920.html b/TestFiles/T0920.html
new file mode 100644
index 0000000..3e9cf61
--- /dev/null
+++ b/TestFiles/T0920.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+p { width: 10em; border: solid aqua; }
+span { width: 5em; height: 5 em; border: solid blue; }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+<p>
+<span> </span>
+Supercalifragilisticexpialidocious
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0921.html b/TestFiles/T0921.html
new file mode 100644
index 0000000..3c71876
--- /dev/null
+++ b/TestFiles/T0921.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<p><img src="T0921_files/img.png"/></p>
+<p>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0921_files/img.PNG b/TestFiles/T0921_files/img.PNG
new file mode 100644
index 0000000..f37ad45
--- /dev/null
+++ b/TestFiles/T0921_files/img.PNG
Binary files differ
diff --git a/TestFiles/T0922.html b/TestFiles/T0922.html
new file mode 100644
index 0000000..99093e2
--- /dev/null
+++ b/TestFiles/T0922.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<img src="T0922_files/img.png"/>
+<p>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0922_files/img.PNG b/TestFiles/T0922_files/img.PNG
new file mode 100644
index 0000000..f37ad45
--- /dev/null
+++ b/TestFiles/T0922_files/img.PNG
Binary files differ
diff --git a/TestFiles/T0923.html b/TestFiles/T0923.html
new file mode 100644
index 0000000..1639f70
--- /dev/null
+++ b/TestFiles/T0923.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+<p><img src="T0923_files/img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0924.html b/TestFiles/T0924.html
new file mode 100644
index 0000000..5a6068d
--- /dev/null
+++ b/TestFiles/T0924.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; }
+</style>
+</head>
+<body>
+<p><img src="T0924_files/img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0924_files/img.PNG b/TestFiles/T0924_files/img.PNG
new file mode 100644
index 0000000..f37ad45
--- /dev/null
+++ b/TestFiles/T0924_files/img.PNG
Binary files differ
diff --git a/TestFiles/T0925.html b/TestFiles/T0925.html
new file mode 100644
index 0000000..f1356ff
--- /dev/null
+++ b/TestFiles/T0925.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: right; }
+</style>
+</head>
+<body>
+<p><img src="T0925_files/img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0925_files/img.PNG b/TestFiles/T0925_files/img.PNG
new file mode 100644
index 0000000..f37ad45
--- /dev/null
+++ b/TestFiles/T0925_files/img.PNG
Binary files differ
diff --git a/TestFiles/T0926.html b/TestFiles/T0926.html
new file mode 100644
index 0000000..b44a04e
--- /dev/null
+++ b/TestFiles/T0926.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document.</p>
+<p><img src="img2.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0927.html b/TestFiles/T0927.html
new file mode 100644
index 0000000..d47612f
--- /dev/null
+++ b/TestFiles/T0927.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+img.a { float: left; }
+img.b { float: right; }
+</style>
+</head>
+<body>
+<p><img class="a" src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document.</p>
+<p><img class="b" src="img2.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0928.html b/TestFiles/T0928.html
new file mode 100644
index 0000000..3e36912
--- /dev/null
+++ b/TestFiles/T0928.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; width: 400px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0929.html b/TestFiles/T0929.html
new file mode 100644
index 0000000..87fb763
--- /dev/null
+++ b/TestFiles/T0929.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; width: 400px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/></p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0930.html b/TestFiles/T0930.html
new file mode 100644
index 0000000..a49d4d1
--- /dev/null
+++ b/TestFiles/T0930.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; height: 80px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/></p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0931.html b/TestFiles/T0931.html
new file mode 100644
index 0000000..095075d
--- /dev/null
+++ b/TestFiles/T0931.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; height: 80px; width: 160px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0932.html b/TestFiles/T0932.html
new file mode 100644
index 0000000..095075d
--- /dev/null
+++ b/TestFiles/T0932.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; height: 80px; width: 160px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0933.html b/TestFiles/T0933.html
new file mode 100644
index 0000000..abac1d0
--- /dev/null
+++ b/TestFiles/T0933.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; margin: 20px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0934.html b/TestFiles/T0934.html
new file mode 100644
index 0000000..0e4cbf0
--- /dev/null
+++ b/TestFiles/T0934.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: left; margin-right: 28px; margin-bottom: 20px; }
+</style>
+</head>
+<body>
+<p><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0935.html b/TestFiles/T0935.html
new file mode 100644
index 0000000..6b1de72
--- /dev/null
+++ b/TestFiles/T0935.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<style type="text/css">
+img { float: right; margin-left: 28px; margin-bottom: 20px; margin-top: 8px; }
+</style>
+</head>
+<body>
+<p style="text-align: justify;"><img src="img.png"/>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0936.html b/TestFiles/T0936.html
new file mode 100644
index 0000000..b07eef9
--- /dev/null
+++ b/TestFiles/T0936.html
@@ -0,0 +1,52 @@
+<html>
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=windows-1252" />
+<meta name="Generator" content="Microsoft Word 15 (filtered)" />
+<style>
+<!--
+ /* Font Definitions */
+ @font-face
+ {font-family:"Cambria Math";
+ panose-1:2 4 5 3 5 4 6 3 2 4;}
+@font-face
+ {font-family:Calibri;
+ panose-1:2 15 5 2 2 2 4 3 2 4;}
+ /* Style Definitions */
+ p.MsoNormal, li.MsoNormal, div.MsoNormal
+ {margin-top:0in;
+ margin-right:0in;
+ margin-bottom:8.0pt;
+ margin-left:0in;
+ line-height:107%;
+ font-size:11.0pt;
+ font-family:"Calibri",sans-serif;}
+.MsoChpDefault
+ {font-family:"Calibri",sans-serif;}
+.MsoPapDefault
+ {margin-bottom:8.0pt;
+ line-height:107%;}
+@page WordSection1
+ {size:8.5in 11.0in;
+ margin:1.0in 1.0in 1.0in 1.0in;}
+div.WordSection1
+ {page:WordSection1;}
+-->
+</style>
+
+</head>
+
+<body lang="EN-US">
+
+<div class="WordSection1">
+
+<p class="MsoNormal"><img width="333" height="52" id="Picture 1"
+src="T0936_files/image001.png" /></p>
+
+<p class="MsoNormal">This is a test.</p>
+
+</div>
+
+</body>
+
+</html>
diff --git a/TestFiles/T0936_files/image001.png b/TestFiles/T0936_files/image001.png
new file mode 100644
index 0000000..19adc40
--- /dev/null
+++ b/TestFiles/T0936_files/image001.png
Binary files differ
diff --git a/TestFiles/T0940.html b/TestFiles/T0940.html
new file mode 100644
index 0000000..cc09150
--- /dev/null
+++ b/TestFiles/T0940.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+img { margin: 2em }
+</style>
+</head>
+<body>
+<p><img style="float: none;" src="img.png"/></p>
+<p>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0945.html b/TestFiles/T0945.html
new file mode 100644
index 0000000..3140d43
--- /dev/null
+++ b/TestFiles/T0945.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+body, p, img { margin: 2em }
+</style>
+</head>
+<body>
+<p><img style="float: none;" src="img.png"/></p>
+<p>On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0948.html b/TestFiles/T0948.html
new file mode 100644
index 0000000..3cfe0cc
--- /dev/null
+++ b/TestFiles/T0948.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+body, p, img { margin: 2em }
+</style>
+</head>
+<body>
+<p><img style="float: left; " src="img.png"/>
+On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. </p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0950.html b/TestFiles/T0950.html
new file mode 100644
index 0000000..e0743e6
--- /dev/null
+++ b/TestFiles/T0950.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+body, p, img { margin: 2em }
+</style>
+</head>
+<body>
+<p>
+<img src="img.png" alt="illustrate floats" style="float: left;"/>
+<img src="img.png" alt="illustrate floats" style="float: right;"/>
+On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0955.html b/TestFiles/T0955.html
new file mode 100644
index 0000000..b52813b
--- /dev/null
+++ b/TestFiles/T0955.html
@@ -0,0 +1,22 @@
+<HTML>
+<HEAD>
+<TITLE>A frame document with CSS 2.1</TITLE>
+<style type="text/css" media="screen">
+body { height: 8.5in }
+#header {
+width: 100%;
+height: 15%;
+top: 0;
+right: 0;
+bottom: auto;
+left: 0;
+}
+</style>
+</HEAD>
+<BODY>
+<DIV id="header" style="background: aqua; border: thin solid blue; padding: 1em;">Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. </DIV>
+<DIV id="sidebar" style="background: green; color: white; border: thin solid blue; padding: 1em;">Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. </DIV>
+<DIV id="main" style="background: orange; border: thin solid blue; padding: 1em;">On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</DIV>
+<DIV id="footer" style="background: yellow; border: thin solid blue; padding: 1em;">Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. </DIV>
+</BODY>
+</HTML>
diff --git a/TestFiles/T0960.html b/TestFiles/T0960.html
new file mode 100644
index 0000000..62bfa82
--- /dev/null
+++ b/TestFiles/T0960.html
@@ -0,0 +1,46 @@
+<HTML>
+<HEAD>
+<TITLE>A frame document with CSS 2.1</TITLE>
+<style type="text/css" media="screen">
+body { height: 8.5in }
+#header {
+width: 100%;
+height: 15%;
+top: 0;
+right: 0;
+bottom: auto;
+left: 0;
+}
+#sidebar {
+width: 10em;
+height: auto;
+top: 15%;
+right: auto;
+bottom: 100px;
+left: 0;
+}
+#main {
+width: auto;
+height: auto;
+top: 15%;
+right: 0;
+bottom: 100px;
+left: 10em;
+}
+#footer {
+width: 100%;
+height: 100px;
+top: auto;
+right: 0;
+bottom: 0;
+left: 0;
+}
+</style>
+</HEAD>
+<BODY>
+<DIV id="header" style="background: aqua; border: thin solid blue; padding: 1em;">Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. Header info. </DIV>
+<DIV id="sidebar" style="background: green; color: white; border: thin solid blue; padding: 1em;">Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. Sidebar. </DIV>
+<DIV id="main" style="background: orange; border: thin solid blue; padding: 1em;">On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly. To change the overall look of your document, choose new Theme elements on the Page Layout tab. To change the looks available in the Quick Style gallery, use the Change Current Quick Style Set command. Both the Themes gallery and the Quick Styles gallery provide reset commands so that you can always restore the look of your document to the original contained in your current template. On the Insert tab, the galleries include items that are designed to coordinate with the overall look of your document. You can use these galleries to insert tables, headers, footers, lists, cover pages, and other document building blocks. When you create pictures, charts, or diagrams, they also coordinate with your current document look. You can easily change the formatting of selected text in the document text by choosing a look for the selected text from the Quick Styles gallery on the Home tab. You can also format text directly by using the other controls on the Home tab. Most controls offer a choice of using the look from the current theme or using a format that you specify directly.</DIV>
+<DIV id="footer" style="background: yellow; border: thin solid blue; padding: 1em;">Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. Footer. </DIV>
+</BODY>
+</HTML>
diff --git a/TestFiles/T0968.html b/TestFiles/T0968.html
new file mode 100644
index 0000000..562b189
--- /dev/null
+++ b/TestFiles/T0968.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+<title>Comparison of positioning schemes</title>
+<style>
+body {
+ font-size:12px;
+/* line-height: 200%;
+ width: 400px;
+ height: 400px; */
+}
+/*
+p { display: block }
+span { display: inline }
+*/
+#outer { color: red }
+#inner { color: blue }
+</style>
+</head>
+<body>
+<p>Beginning of body contents.
+<span id="outer"> Start of outer contents.
+<span id="inner"> Inner contents.</span>
+End of outer contents.</span>
+End of body contents.
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T0970.html b/TestFiles/T0970.html
new file mode 100644
index 0000000..9cd7e31
--- /dev/null
+++ b/TestFiles/T0970.html
@@ -0,0 +1,21 @@
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#outer { color: red }
+#inner { color: blue }
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0980.html b/TestFiles/T0980.html
new file mode 100644
index 0000000..8c84936
--- /dev/null
+++ b/TestFiles/T0980.html
@@ -0,0 +1,21 @@
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#outer { position: relative; top: -12px; color: red }
+#inner { position: relative; top: 12px; color: blue }
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T0990.html b/TestFiles/T0990.html
new file mode 100644
index 0000000..3615298
--- /dev/null
+++ b/TestFiles/T0990.html
@@ -0,0 +1,21 @@
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#outer { color: red }
+#inner { float: right; width: 130px; color: blue }
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1000.html b/TestFiles/T1000.html
new file mode 100644
index 0000000..47277b4
--- /dev/null
+++ b/TestFiles/T1000.html
@@ -0,0 +1,23 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#inner { float: right; width: 130px; color: blue }
+#sibling { color: red }
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+<SPAN id="sibling"> Sibling contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1010.html b/TestFiles/T1010.html
new file mode 100644
index 0000000..a4ab466
--- /dev/null
+++ b/TestFiles/T1010.html
@@ -0,0 +1,23 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#inner { float: right; width: 130px; color: blue }
+#sibling { clear: right; color: red }
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+<SPAN id="sibling"> Sibling contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1020.html b/TestFiles/T1020.html
new file mode 100644
index 0000000..cdec2cd
--- /dev/null
+++ b/TestFiles/T1020.html
@@ -0,0 +1,28 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#outer {
+position: absolute;
+top: 200px; left: 200px;
+width: 200px;
+color: red;
+}
+#inner { color: blue }
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+<SPAN id="sibling"> Sibling contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1030.html b/TestFiles/T1030.html
new file mode 100644
index 0000000..e256559
--- /dev/null
+++ b/TestFiles/T1030.html
@@ -0,0 +1,31 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#outer {
+position: relative;
+color: red
+}
+#inner {
+position: absolute;
+top: 200px; left: -100px;
+height: 130px; width: 130px;
+color: blue;
+}
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+<SPAN id="sibling"> Sibling contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1040.html b/TestFiles/T1040.html
new file mode 100644
index 0000000..199f6fc
--- /dev/null
+++ b/TestFiles/T1040.html
@@ -0,0 +1,28 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+<style>
+body { display: block; font-size:12px; line-height: 200%;
+width: 400px; height: 400px }
+p { display: block }
+span { display: inline }
+#outer { color: red }
+#inner {
+position: absolute;
+top: 200px; left: -100px;
+height: 130px; width: 130px;
+color: blue;
+}
+</style>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+<SPAN id="sibling"> Sibling contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1050.html b/TestFiles/T1050.html
new file mode 100644
index 0000000..b1bac15
--- /dev/null
+++ b/TestFiles/T1050.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <P style="position: relative; margin-right: 10px; left: 10px;">
+I used two red hyphens to serve as a change bar. They
+will "float" to the left of the line containing THIS
+<SPAN style="position: absolute; top: auto; left: -1em; color: red;">--</SPAN>
+word.</P>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1060.html b/TestFiles/T1060.html
new file mode 100644
index 0000000..e3650e3
--- /dev/null
+++ b/TestFiles/T1060.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<style type="text/css">
+p.note:before { content: "Note: " }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p class="note">fzz</p>
+ <p>fzz</p>
+ <p class="note">fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1070.html b/TestFiles/T1070.html
new file mode 100644
index 0000000..58c8527
--- /dev/null
+++ b/TestFiles/T1070.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+<style type="text/css">
+body {
+counter-reset: chapter;
+}
+h1:before {
+content: "Chapter " counter(chapter) ". ";
+counter-increment: chapter;
+}
+h1 {
+counter-reset: section;
+}
+h2:before {
+content: counter(chapter) "." counter(section) " ";
+counter-increment: section;
+}
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p class="note">fzz</p>
+ <p>fzz</p>
+ <p class="note">fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1080.html b/TestFiles/T1080.html
new file mode 100644
index 0000000..76e7016
--- /dev/null
+++ b/TestFiles/T1080.html
@@ -0,0 +1,16 @@
+
+<HTML>
+<HEAD>
+<TITLE>Lowercase latin numbering</TITLE>
+<style type="text/css">
+ol { list-style-type: lower-roman }
+</style>
+</HEAD>
+<BODY>
+<ol>
+<li> This is the first item.</li>
+<li> This is the second item.</li>
+<li> This is the third item.</li>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1100.html b/TestFiles/T1100.html
new file mode 100644
index 0000000..941c796
--- /dev/null
+++ b/TestFiles/T1100.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+body { font-family: Gill, Helvetica, sans-serif }
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1110.html b/TestFiles/T1110.html
new file mode 100644
index 0000000..6b9c492
--- /dev/null
+++ b/TestFiles/T1110.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="font-size: xx-small">xx-small Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: x-small">x-small Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: small">small Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: medium">medium Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: large">large Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: x-large">x-large Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: xx-large">xx-large Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: larger">larger Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: smaller">smaller Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: 80%">80% Now is the time for all good men to come to the aid of their country.</p>
+ <p style="font-size: 120%">120% Now is the time for all good men to come to the aid of their country.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1111.html b/TestFiles/T1111.html
new file mode 100644
index 0000000..b760877
--- /dev/null
+++ b/TestFiles/T1111.html
@@ -0,0 +1,136 @@
+<html>
+<head>
+<style type="text/css">
+.f06 { font-size: 06pt; }
+.f07 { font-size: 07pt; }
+.f08 { font-size: 08pt; }
+.f09 { font-size: 09pt; }
+.f10 { font-size: 10pt; }
+.f11 { font-size: 11pt; }
+.f12 { font-size: 12pt; }
+.f13 { font-size: 13pt; }
+.f14 { font-size: 14pt; }
+.f15 { font-size: 15pt; }
+.f16 { font-size: 16pt; }
+.f17 { font-size: 17pt; }
+.f18 { font-size: 18pt; }
+.f19 { font-size: 19pt; }
+.f20 { font-size: 20pt; }
+.f21 { font-size: 21pt; }
+.f22 { font-size: 22pt; }
+.f23 { font-size: 23pt; }
+.f24 { font-size: 24pt; }
+.f25 { font-size: 25pt; }
+.f26 { font-size: 26pt; }
+.f27 { font-size: 27pt; }
+.f28 { font-size: 28pt; }
+.f29 { font-size: 29pt; }
+.f30 { font-size: 30pt; }
+.f31 { font-size: 31pt; }
+.f32 { font-size: 32pt; }
+.f33 { font-size: 33pt; }
+.f34 { font-size: 34pt; }
+.f35 { font-size: 35pt; }
+.f36 { font-size: 36pt; }
+.f37 { font-size: 37pt; }
+.f38 { font-size: 38pt; }
+.f39 { font-size: 39pt; }
+.f40 { font-size: 40pt; }
+.f41 { font-size: 41pt; }
+.f42 { font-size: 42pt; }
+.f43 { font-size: 43pt; }
+.f44 { font-size: 44pt; }
+.f45 { font-size: 45pt; }
+.f46 { font-size: 46pt; }
+.f47 { font-size: 47pt; }
+.f48 { font-size: 48pt; }
+.f49 { font-size: 49pt; }
+.f50 { font-size: 50pt; }
+.f51 { font-size: 51pt; }
+.f52 { font-size: 52pt; }
+.f53 { font-size: 53pt; }
+.f54 { font-size: 54pt; }
+.f55 { font-size: 55pt; }
+.f56 { font-size: 56pt; }
+.f57 { font-size: 57pt; }
+.f58 { font-size: 58pt; }
+.f59 { font-size: 59pt; }
+.f60 { font-size: 60pt; }
+.f62 { font-size: 62pt; }
+.f64 { font-size: 64pt; }
+.f66 { font-size: 66pt; }
+.f68 { font-size: 68pt; }
+.f70 { font-size: 70pt; }
+.f72 { font-size: 72pt; }
+.f74 { font-size: 74pt; }
+.f76 { font-size: 76pt; }
+.f78 { font-size: 78pt; }
+</style>
+</head>
+<body>
+ <div class="f06"><p>06pt font</p> <p style="font-size: larger">06pt font larger</p></div>
+ <div class="f07"><p>07pt font</p> <p style="font-size: larger">07pt font larger</p></div>
+ <div class="f08"><p>08pt font</p> <p style="font-size: larger">08pt font larger</p></div>
+ <div class="f09"><p>09pt font</p> <p style="font-size: larger">09pt font larger</p></div>
+ <div class="f10"><p>10pt font</p> <p style="font-size: larger">10pt font larger</p></div>
+ <div class="f11"><p>11pt font</p> <p style="font-size: larger">11pt font larger</p></div>
+ <div class="f12"><p>12pt font</p> <p style="font-size: larger">12pt font larger</p></div>
+ <div class="f13"><p>13pt font</p> <p style="font-size: larger">13pt font larger</p></div>
+ <div class="f14"><p>14pt font</p> <p style="font-size: larger">14pt font larger</p></div>
+ <div class="f15"><p>15pt font</p> <p style="font-size: larger">15pt font larger</p></div>
+ <div class="f16"><p>16pt font</p> <p style="font-size: larger">16pt font larger</p></div>
+ <div class="f17"><p>17pt font</p> <p style="font-size: larger">17pt font larger</p></div>
+ <div class="f18"><p>18pt font</p> <p style="font-size: larger">18pt font larger</p></div>
+ <div class="f19"><p>19pt font</p> <p style="font-size: larger">19pt font larger</p></div>
+ <div class="f20"><p>20pt font</p> <p style="font-size: larger">20pt font larger</p></div>
+ <div class="f21"><p>21pt font</p> <p style="font-size: larger">21pt font larger</p></div>
+ <div class="f22"><p>22pt font</p> <p style="font-size: larger">22pt font larger</p></div>
+ <div class="f23"><p>23pt font</p> <p style="font-size: larger">23pt font larger</p></div>
+ <div class="f24"><p>24pt font</p> <p style="font-size: larger">24pt font larger</p></div>
+ <div class="f25"><p>25pt font</p> <p style="font-size: larger">25pt font larger</p></div>
+ <div class="f26"><p>26pt font</p> <p style="font-size: larger">26pt font larger</p></div>
+ <div class="f27"><p>27pt font</p> <p style="font-size: larger">27pt font larger</p></div>
+ <div class="f28"><p>28pt font</p> <p style="font-size: larger">28pt font larger</p></div>
+ <div class="f29"><p>29pt font</p> <p style="font-size: larger">29pt font larger</p></div>
+ <div class="f30"><p>30pt font</p> <p style="font-size: larger">30pt font larger</p></div>
+ <div class="f31"><p>31pt font</p> <p style="font-size: larger">31pt font larger</p></div>
+ <div class="f32"><p>32pt font</p> <p style="font-size: larger">32pt font larger</p></div>
+ <div class="f33"><p>33pt font</p> <p style="font-size: larger">33pt font larger</p></div>
+ <div class="f34"><p>34pt font</p> <p style="font-size: larger">34pt font larger</p></div>
+ <div class="f35"><p>35pt font</p> <p style="font-size: larger">35pt font larger</p></div>
+ <div class="f36"><p>36pt font</p> <p style="font-size: larger">36pt font larger</p></div>
+ <div class="f37"><p>37pt font</p> <p style="font-size: larger">37pt font larger</p></div>
+ <div class="f38"><p>38pt font</p> <p style="font-size: larger">38pt font larger</p></div>
+ <div class="f39"><p>39pt font</p> <p style="font-size: larger">39pt font larger</p></div>
+ <div class="f40"><p>40pt font</p> <p style="font-size: larger">40pt font larger</p></div>
+ <div class="f41"><p>41pt font</p> <p style="font-size: larger">41pt font larger</p></div>
+ <div class="f42"><p>42pt font</p> <p style="font-size: larger">42pt font larger</p></div>
+ <div class="f43"><p>43pt font</p> <p style="font-size: larger">43pt font larger</p></div>
+ <div class="f44"><p>44pt font</p> <p style="font-size: larger">44pt font larger</p></div>
+ <div class="f45"><p>45pt font</p> <p style="font-size: larger">45pt font larger</p></div>
+ <div class="f46"><p>46pt font</p> <p style="font-size: larger">46pt font larger</p></div>
+ <div class="f47"><p>47pt font</p> <p style="font-size: larger">47pt font larger</p></div>
+ <div class="f48"><p>48pt font</p> <p style="font-size: larger">48pt font larger</p></div>
+ <div class="f49"><p>49pt font</p> <p style="font-size: larger">49pt font larger</p></div>
+ <div class="f50"><p>50pt font</p> <p style="font-size: larger">50pt font larger</p></div>
+ <div class="f51"><p>51pt font</p> <p style="font-size: larger">51pt font larger</p></div>
+ <div class="f52"><p>52pt font</p> <p style="font-size: larger">52pt font larger</p></div>
+ <div class="f53"><p>53pt font</p> <p style="font-size: larger">53pt font larger</p></div>
+ <div class="f54"><p>54pt font</p> <p style="font-size: larger">54pt font larger</p></div>
+ <div class="f55"><p>55pt font</p> <p style="font-size: larger">55pt font larger</p></div>
+ <div class="f56"><p>56pt font</p> <p style="font-size: larger">56pt font larger</p></div>
+ <div class="f57"><p>57pt font</p> <p style="font-size: larger">57pt font larger</p></div>
+ <div class="f58"><p>58pt font</p> <p style="font-size: larger">58pt font larger</p></div>
+ <div class="f59"><p>59pt font</p> <p style="font-size: larger">59pt font larger</p></div>
+ <div class="f60"><p>60pt font</p> <p style="font-size: larger">60pt font larger</p></div>
+ <div class="f62"><p>62pt font</p> <p style="font-size: larger">62pt font larger</p></div>
+ <div class="f64"><p>64pt font</p> <p style="font-size: larger">64pt font larger</p></div>
+ <div class="f66"><p>66pt font</p> <p style="font-size: larger">66pt font larger</p></div>
+ <div class="f68"><p>68pt font</p> <p style="font-size: larger">68pt font larger</p></div>
+ <div class="f70"><p>70pt font</p> <p style="font-size: larger">70pt font larger</p></div>
+ <div class="f72"><p>72pt font</p> <p style="font-size: larger">72pt font larger</p></div>
+ <div class="f74"><p>74pt font</p> <p style="font-size: larger">74pt font larger</p></div>
+ <div class="f76"><p>76pt font</p> <p style="font-size: larger">76pt font larger</p></div>
+ <div class="f78"><p>78pt font</p> <p style="font-size: larger">78pt font larger</p></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1112.html b/TestFiles/T1112.html
new file mode 100644
index 0000000..af1cb2f
--- /dev/null
+++ b/TestFiles/T1112.html
@@ -0,0 +1,136 @@
+<html>
+<head>
+<style type="text/css">
+.f06 { font-size: 06pt; }
+.f07 { font-size: 07pt; }
+.f08 { font-size: 08pt; }
+.f09 { font-size: 09pt; }
+.f10 { font-size: 10pt; }
+.f11 { font-size: 11pt; }
+.f12 { font-size: 12pt; }
+.f13 { font-size: 13pt; }
+.f14 { font-size: 14pt; }
+.f15 { font-size: 15pt; }
+.f16 { font-size: 16pt; }
+.f17 { font-size: 17pt; }
+.f18 { font-size: 18pt; }
+.f19 { font-size: 19pt; }
+.f20 { font-size: 20pt; }
+.f21 { font-size: 21pt; }
+.f22 { font-size: 22pt; }
+.f23 { font-size: 23pt; }
+.f24 { font-size: 24pt; }
+.f25 { font-size: 25pt; }
+.f26 { font-size: 26pt; }
+.f27 { font-size: 27pt; }
+.f28 { font-size: 28pt; }
+.f29 { font-size: 29pt; }
+.f30 { font-size: 30pt; }
+.f31 { font-size: 31pt; }
+.f32 { font-size: 32pt; }
+.f33 { font-size: 33pt; }
+.f34 { font-size: 34pt; }
+.f35 { font-size: 35pt; }
+.f36 { font-size: 36pt; }
+.f37 { font-size: 37pt; }
+.f38 { font-size: 38pt; }
+.f39 { font-size: 39pt; }
+.f40 { font-size: 40pt; }
+.f41 { font-size: 41pt; }
+.f42 { font-size: 42pt; }
+.f43 { font-size: 43pt; }
+.f44 { font-size: 44pt; }
+.f45 { font-size: 45pt; }
+.f46 { font-size: 46pt; }
+.f47 { font-size: 47pt; }
+.f48 { font-size: 48pt; }
+.f49 { font-size: 49pt; }
+.f50 { font-size: 50pt; }
+.f51 { font-size: 51pt; }
+.f52 { font-size: 52pt; }
+.f53 { font-size: 53pt; }
+.f54 { font-size: 54pt; }
+.f55 { font-size: 55pt; }
+.f56 { font-size: 56pt; }
+.f57 { font-size: 57pt; }
+.f58 { font-size: 58pt; }
+.f59 { font-size: 59pt; }
+.f60 { font-size: 60pt; }
+.f62 { font-size: 62pt; }
+.f64 { font-size: 64pt; }
+.f66 { font-size: 66pt; }
+.f68 { font-size: 68pt; }
+.f70 { font-size: 70pt; }
+.f72 { font-size: 72pt; }
+.f74 { font-size: 74pt; }
+.f76 { font-size: 76pt; }
+.f78 { font-size: 78pt; }
+</style>
+</head>
+<body>
+ <div class="f06"><p>06pt font</p> <p style="font-size: smaller">06pt font smaller</p></div>
+ <div class="f07"><p>07pt font</p> <p style="font-size: smaller">07pt font smaller</p></div>
+ <div class="f08"><p>08pt font</p> <p style="font-size: smaller">08pt font smaller</p></div>
+ <div class="f09"><p>09pt font</p> <p style="font-size: smaller">09pt font smaller</p></div>
+ <div class="f10"><p>10pt font</p> <p style="font-size: smaller">10pt font smaller</p></div>
+ <div class="f11"><p>11pt font</p> <p style="font-size: smaller">11pt font smaller</p></div>
+ <div class="f12"><p>12pt font</p> <p style="font-size: smaller">12pt font smaller</p></div>
+ <div class="f13"><p>13pt font</p> <p style="font-size: smaller">13pt font smaller</p></div>
+ <div class="f14"><p>14pt font</p> <p style="font-size: smaller">14pt font smaller</p></div>
+ <div class="f15"><p>15pt font</p> <p style="font-size: smaller">15pt font smaller</p></div>
+ <div class="f16"><p>16pt font</p> <p style="font-size: smaller">16pt font smaller</p></div>
+ <div class="f17"><p>17pt font</p> <p style="font-size: smaller">17pt font smaller</p></div>
+ <div class="f18"><p>18pt font</p> <p style="font-size: smaller">18pt font smaller</p></div>
+ <div class="f19"><p>19pt font</p> <p style="font-size: smaller">19pt font smaller</p></div>
+ <div class="f20"><p>20pt font</p> <p style="font-size: smaller">20pt font smaller</p></div>
+ <div class="f21"><p>21pt font</p> <p style="font-size: smaller">21pt font smaller</p></div>
+ <div class="f22"><p>22pt font</p> <p style="font-size: smaller">22pt font smaller</p></div>
+ <div class="f23"><p>23pt font</p> <p style="font-size: smaller">23pt font smaller</p></div>
+ <div class="f24"><p>24pt font</p> <p style="font-size: smaller">24pt font smaller</p></div>
+ <div class="f25"><p>25pt font</p> <p style="font-size: smaller">25pt font smaller</p></div>
+ <div class="f26"><p>26pt font</p> <p style="font-size: smaller">26pt font smaller</p></div>
+ <div class="f27"><p>27pt font</p> <p style="font-size: smaller">27pt font smaller</p></div>
+ <div class="f28"><p>28pt font</p> <p style="font-size: smaller">28pt font smaller</p></div>
+ <div class="f29"><p>29pt font</p> <p style="font-size: smaller">29pt font smaller</p></div>
+ <div class="f30"><p>30pt font</p> <p style="font-size: smaller">30pt font smaller</p></div>
+ <div class="f31"><p>31pt font</p> <p style="font-size: smaller">31pt font smaller</p></div>
+ <div class="f32"><p>32pt font</p> <p style="font-size: smaller">32pt font smaller</p></div>
+ <div class="f33"><p>33pt font</p> <p style="font-size: smaller">33pt font smaller</p></div>
+ <div class="f34"><p>34pt font</p> <p style="font-size: smaller">34pt font smaller</p></div>
+ <div class="f35"><p>35pt font</p> <p style="font-size: smaller">35pt font smaller</p></div>
+ <div class="f36"><p>36pt font</p> <p style="font-size: smaller">36pt font smaller</p></div>
+ <div class="f37"><p>37pt font</p> <p style="font-size: smaller">37pt font smaller</p></div>
+ <div class="f38"><p>38pt font</p> <p style="font-size: smaller">38pt font smaller</p></div>
+ <div class="f39"><p>39pt font</p> <p style="font-size: smaller">39pt font smaller</p></div>
+ <div class="f40"><p>40pt font</p> <p style="font-size: smaller">40pt font smaller</p></div>
+ <div class="f41"><p>41pt font</p> <p style="font-size: smaller">41pt font smaller</p></div>
+ <div class="f42"><p>42pt font</p> <p style="font-size: smaller">42pt font smaller</p></div>
+ <div class="f43"><p>43pt font</p> <p style="font-size: smaller">43pt font smaller</p></div>
+ <div class="f44"><p>44pt font</p> <p style="font-size: smaller">44pt font smaller</p></div>
+ <div class="f45"><p>45pt font</p> <p style="font-size: smaller">45pt font smaller</p></div>
+ <div class="f46"><p>46pt font</p> <p style="font-size: smaller">46pt font smaller</p></div>
+ <div class="f47"><p>47pt font</p> <p style="font-size: smaller">47pt font smaller</p></div>
+ <div class="f48"><p>48pt font</p> <p style="font-size: smaller">48pt font smaller</p></div>
+ <div class="f49"><p>49pt font</p> <p style="font-size: smaller">49pt font smaller</p></div>
+ <div class="f50"><p>50pt font</p> <p style="font-size: smaller">50pt font smaller</p></div>
+ <div class="f51"><p>51pt font</p> <p style="font-size: smaller">51pt font smaller</p></div>
+ <div class="f52"><p>52pt font</p> <p style="font-size: smaller">52pt font smaller</p></div>
+ <div class="f53"><p>53pt font</p> <p style="font-size: smaller">53pt font smaller</p></div>
+ <div class="f54"><p>54pt font</p> <p style="font-size: smaller">54pt font smaller</p></div>
+ <div class="f55"><p>55pt font</p> <p style="font-size: smaller">55pt font smaller</p></div>
+ <div class="f56"><p>56pt font</p> <p style="font-size: smaller">56pt font smaller</p></div>
+ <div class="f57"><p>57pt font</p> <p style="font-size: smaller">57pt font smaller</p></div>
+ <div class="f58"><p>58pt font</p> <p style="font-size: smaller">58pt font smaller</p></div>
+ <div class="f59"><p>59pt font</p> <p style="font-size: smaller">59pt font smaller</p></div>
+ <div class="f60"><p>60pt font</p> <p style="font-size: smaller">60pt font smaller</p></div>
+ <div class="f62"><p>62pt font</p> <p style="font-size: smaller">62pt font smaller</p></div>
+ <div class="f64"><p>64pt font</p> <p style="font-size: smaller">64pt font smaller</p></div>
+ <div class="f66"><p>66pt font</p> <p style="font-size: smaller">66pt font smaller</p></div>
+ <div class="f68"><p>68pt font</p> <p style="font-size: smaller">68pt font smaller</p></div>
+ <div class="f70"><p>70pt font</p> <p style="font-size: smaller">70pt font smaller</p></div>
+ <div class="f72"><p>72pt font</p> <p style="font-size: smaller">72pt font smaller</p></div>
+ <div class="f74"><p>74pt font</p> <p style="font-size: smaller">74pt font smaller</p></div>
+ <div class="f76"><p>76pt font</p> <p style="font-size: smaller">76pt font smaller</p></div>
+ <div class="f78"><p>78pt font</p> <p style="font-size: smaller">78pt font smaller</p></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1120.html b/TestFiles/T1120.html
new file mode 100644
index 0000000..db1766a
--- /dev/null
+++ b/TestFiles/T1120.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="text-decoration: none">On the Insert tab, the galleries include items.</p>
+ <p style="text-decoration: underline">On the Insert tab, the galleries include items.</p>
+ <p style="text-decoration: overline">On the Insert tab, the galleries include items.</p>
+ <p style="text-decoration: line-through">On the Insert tab, the galleries include items.</p>
+ <p style="text-decoration: blink">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1130.html b/TestFiles/T1130.html
new file mode 100644
index 0000000..ecf5351
--- /dev/null
+++ b/TestFiles/T1130.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="letter-spacing: normal">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: 1px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: 2px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: 3px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: -1px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: -2px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: -3px">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1131.html b/TestFiles/T1131.html
new file mode 100644
index 0000000..f485de2
--- /dev/null
+++ b/TestFiles/T1131.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <p style="letter-spacing: 110%">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1132.html b/TestFiles/T1132.html
new file mode 100644
index 0000000..a704e2b
--- /dev/null
+++ b/TestFiles/T1132.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <p style="letter-spacing: -1px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: -2px">On the Insert tab, the galleries include items.</p>
+ <p style="letter-spacing: -3px">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1140.html b/TestFiles/T1140.html
new file mode 100644
index 0000000..004ac9d
--- /dev/null
+++ b/TestFiles/T1140.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="word-spacing: normal">On the Insert tab, the galleries include items.</p>
+ <p style="word-spacing: 1px">On the Insert tab, the galleries include items.</p>
+ <p style="word-spacing: 2px">On the Insert tab, the galleries include items.</p>
+ <p style="word-spacing: 3px">On the Insert tab, the galleries include items.</p>
+ <p style="word-spacing: -1px">On the Insert tab, the galleries include items.</p>
+ <p style="word-spacing: -2px">On the Insert tab, the galleries include items.</p>
+ <p style="word-spacing: -3px">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1150.html b/TestFiles/T1150.html
new file mode 100644
index 0000000..6458986
--- /dev/null
+++ b/TestFiles/T1150.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="text-transform: none">On the Insert tab, the galleries include items.</p>
+ <p style="text-transform: capitalize">On the Insert tab, the galleries include items.</p>
+ <p style="text-transform: uppercase">On the Insert tab, the galleries include items.</p>
+ <p style="text-transform: lowercase">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1160.html b/TestFiles/T1160.html
new file mode 100644
index 0000000..5bcd622
--- /dev/null
+++ b/TestFiles/T1160.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p style="white-space: normal">On the Insert tab, the galleries include items.</p>
+ <p style="white-space: pre">On the Insert tab, the galleries include items.</p>
+ <p style="white-space: nowrap">On the Insert tab, the galleries include items.</p>
+ <p style="white-space: pre-wrap">On the Insert tab, the galleries include items.</p>
+ <p style="white-space: pre-line">On the Insert tab, the galleries include items.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1170.html b/TestFiles/T1170.html
new file mode 100644
index 0000000..15067cc
--- /dev/null
+++ b/TestFiles/T1170.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body style="font: calibri 11pt normal;">
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+<p>Test</p>
+<p>Test</p>
+<p>Test</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1180.html b/TestFiles/T1180.html
new file mode 100644
index 0000000..a631c44
--- /dev/null
+++ b/TestFiles/T1180.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+th { text-align: center; font-weight: bold; color: red; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1190.html b/TestFiles/T1190.html
new file mode 100644
index 0000000..08a1509
--- /dev/null
+++ b/TestFiles/T1190.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+th { vertical-align: baseline }
+td { vertical-align: middle }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1200.html b/TestFiles/T1200.html
new file mode 100644
index 0000000..9a1cb2d
--- /dev/null
+++ b/TestFiles/T1200.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { border-collapse: collapse }
+table, th, td { border: thin solid black }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1201.html b/TestFiles/T1201.html
new file mode 100644
index 0000000..31ba6cb
--- /dev/null
+++ b/TestFiles/T1201.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { border-collapse: collapse }
+th, td { border: thin solid black }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1210.html b/TestFiles/T1210.html
new file mode 100644
index 0000000..a84b3db
--- /dev/null
+++ b/TestFiles/T1210.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1220.html b/TestFiles/T1220.html
new file mode 100644
index 0000000..9d085cd
--- /dev/null
+++ b/TestFiles/T1220.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { table-layout: fixed; width: 100%; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1230.html b/TestFiles/T1230.html
new file mode 100644
index 0000000..221a20b
--- /dev/null
+++ b/TestFiles/T1230.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { table-layout: fixed; }
+th { width: 10em }
+td { width: 6em }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1240.html b/TestFiles/T1240.html
new file mode 100644
index 0000000..6009035
--- /dev/null
+++ b/TestFiles/T1240.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { table-layout: fixed; }
+th { width: 10em; }
+td { width: 6em; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption style="border: thin solid red;">This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ <p>Word is incorrectly calculating column widths. Word is also using the wrong font for the table. Word always uses 11pt calibri regardless
+ of font set for the table, or body. Word always calculates column widths (when speced by em or ex) based on 12pt calibri.</p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1241.html b/TestFiles/T1241.html
new file mode 100644
index 0000000..b076186
--- /dev/null
+++ b/TestFiles/T1241.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style type="text/css">
+body, table { font: calibri 12pt; }
+table { table-layout: fixed; }
+th { width: 10em; }
+td { width: 6em; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1242.html b/TestFiles/T1242.html
new file mode 100644
index 0000000..4f32993
--- /dev/null
+++ b/TestFiles/T1242.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style type="text/css">
+body { font: calibri 11pt; }
+table { table-layout: fixed; }
+th { width: 72pt; }
+td { width: 50pt; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1250.html b/TestFiles/T1250.html
new file mode 100644
index 0000000..75f9361
--- /dev/null
+++ b/TestFiles/T1250.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<style type="text/css">
+table, body { font: calibri 11pt }
+table { table-layout: fixed }
+th { width: 10em; margin: 1em; }
+td { width: 6em; margin: 1em; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th><td>Cell 1 </td><td>Cell 2</td></tr>
+<tr id="row2">
+<th>Header 2 </th><td>Cell 3 </td><td>Cell 4</td></tr>
+<tr id="row3">
+<th>Header 3 </th><td>Cell 5 </td><td>Cell 6</td></tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1251.html b/TestFiles/T1251.html
new file mode 100644
index 0000000..f48206b
--- /dev/null
+++ b/TestFiles/T1251.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style type="text/css">
+table, body { font: calibri 11pt; }
+table { table-layout: fixed; border: medium solid red; }
+td { width: 6em; border: thin solid blue; margin: 15px; }
+</style>
+</head>
+<body>
+<table>
+<tr>
+ <td>Cell 1</td>
+</tr>
+</table>
+ </body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1260.html b/TestFiles/T1260.html
new file mode 100644
index 0000000..21cace8
--- /dev/null
+++ b/TestFiles/T1260.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed }
+th { width: 10em; margin: 1em; }
+td { width: 6em; margin: 1em; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th>
+<td colspan='2'>Cell 1 </td>
+<td>Cell 2</td>
+</tr>
+<tr id="row2">
+<th>Header 2 </th>
+<td rowspan='2'>Cell 3 </td>
+<td colspan='2'>Cell 4</td>
+</tr>
+<tr id="row3">
+<th>Header 3 </th>
+<td>Cell 5 </td>
+<td>Cell 6 </td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1270.html b/TestFiles/T1270.html
new file mode 100644
index 0000000..0d9419b
--- /dev/null
+++ b/TestFiles/T1270.html
@@ -0,0 +1,26 @@
+
+<HTML>
+<HEAD>
+<TITLE>Table example</TITLE>
+<style type="text/css">
+TABLE { background: #ff0; border: solid black;
+empty-cells: hide }
+TR.top { background: red }
+TD { border: solid black }
+</style>
+</HEAD>
+<BODY>
+<TABLE>
+<TR CLASS="top">
+<TD> 1</TD>
+<TD rowspan="2"> 2</TD>
+<TD> 3</TD>
+<TD> 4</TD>
+</TR>
+<TR>
+<TD> 5</TD>
+<TD></TD>
+</TR>
+</TABLE>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1280.html b/TestFiles/T1280.html
new file mode 100644
index 0000000..1f6e473
--- /dev/null
+++ b/TestFiles/T1280.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<style type="text/css">
+table { width: 40em; }
+th { width: 10em; margin: 1em; }
+td { width: 6em; margin: 1em; }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th>
+<td colspan='2'>Cell 1 </td>
+<td>Cell 2</td>
+</tr>
+<tr id="row2">
+<th>Header 2 </th>
+<td rowspan='2'>Cell 3 </td>
+<td colspan='2'>Cell 4</td>
+</tr>
+<tr id="row3">
+<th>Header 3 </th>
+<td>Cell 5 </td>
+<td>Cell 6 </td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1290.html b/TestFiles/T1290.html
new file mode 100644
index 0000000..7d2b332
--- /dev/null
+++ b/TestFiles/T1290.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed;
+margin-left: 2em;
+margin-right: 2em }
+table { border: medium solid red; }
+th { border: thin solid green; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th>
+<td colspan='2'>Cell 1 </td>
+<td>Cell 2</td>
+</tr>
+<tr id="row2">
+<th>Header 2 </th>
+<td rowspan='2'>Cell 3 </td>
+<td colspan='2'>Cell 4</td>
+</tr>
+<tr id="row3">
+<th>Header 3 </th>
+<td>Cell 5 </td>
+<td>Cell 6 </td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1300.html b/TestFiles/T1300.html
new file mode 100644
index 0000000..01accf0
--- /dev/null
+++ b/TestFiles/T1300.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed;
+margin-left: 2em;
+margin-right: 2em;
+border-collapse: collapse;
+}
+table { border: medium solid red; }
+th { border: thin solid green; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th>
+<td colspan='2'>Cell 1 </td>
+<td>Cell 2</td>
+</tr>
+<tr id="row2">
+<th>Header 2 </th>
+<td rowspan='2'>Cell 3 </td>
+<td colspan='2'>Cell 4</td>
+</tr>
+<tr id="row3">
+<th>Header 3 </th>
+<td>Cell 5 </td>
+<td>Cell 6 </td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1310.html b/TestFiles/T1310.html
new file mode 100644
index 0000000..5fd9af9
--- /dev/null
+++ b/TestFiles/T1310.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed;
+ margin-left: 2em;
+ margin-right: 2em;
+ border-spacing: 10px;
+}
+table { border: medium solid red; }
+th { border: thin solid green; }
+td { border: thin solid blue; }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th>
+<td colspan='2'>Cell 1 </td>
+<td>Cell 2</td>
+</tr>
+<tr id="row2">
+<th>Header 2 </th>
+<td rowspan='2'>Cell 3 </td>
+<td colspan='2'>Cell 4</td>
+</tr>
+<tr id="row3">
+<th>Header 3 </th>
+<td>Cell 5 </td>
+<td>Cell 6 </td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1320.html b/TestFiles/T1320.html
new file mode 100644
index 0000000..aeb9cf9
--- /dev/null
+++ b/TestFiles/T1320.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<style type="text/css">
+table { border: outset 10pt;
+border-collapse: separate;
+border-spacing: 15pt }
+td { border: inset 5pt }
+td.special { border: inset 10pt }
+</style>
+</head>
+<body>
+<table>
+<caption>This is a simple 3x3 table</caption>
+<tr id="row1">
+<th>Header 1 </th>
+<td colspan='2' class='special'>Cell 1 </td>
+<td>Cell 2</td>
+</tr>
+<tr id="row2">
+<th>Header 2 </th>
+<td rowspan='2'>Cell 3 </td>
+<td colspan='2'>Cell 4</td>
+</tr>
+<tr id="row3">
+<th>Header 3 </th>
+<td>Cell 5 </td>
+<td>Cell 6 </td>
+</tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1330.html b/TestFiles/T1330.html
new file mode 100644
index 0000000..73600d3
--- /dev/null
+++ b/TestFiles/T1330.html
@@ -0,0 +1,32 @@
+
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+th { border: thin solid aqua; }
+</style>
+</head>
+<body>
+<p>table-layout: fixed</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th>Header 1 </th>
+ <td>Cell 1 </td>
+ <td>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1340.html b/TestFiles/T1340.html
new file mode 100644
index 0000000..52fd1ad
--- /dev/null
+++ b/TestFiles/T1340.html
@@ -0,0 +1,32 @@
+
+<html>
+<head>
+<style type="text/css">
+table { table-layout: auto }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+th { border: thin solid aqua; }
+</style>
+</head>
+<body>
+<p>table-layout: auto</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th>Header 1 </th>
+ <td>Cell 1 </td>
+ <td>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1350.html b/TestFiles/T1350.html
new file mode 100644
index 0000000..18b98df
--- /dev/null
+++ b/TestFiles/T1350.html
@@ -0,0 +1,35 @@
+
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed;
+margin-left: 2em;
+margin-right: 2em }
+
+table { border: medium solid red; }
+td { border: thin solid blue; }
+th { border: thin solid aqua; }
+</style>
+</head>
+<body>
+<p>table-layout: auto</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th>Header 1 </th>
+ <td>Cell 1 </td>
+ <td>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1360.html b/TestFiles/T1360.html
new file mode 100644
index 0000000..aa7fbfc
--- /dev/null
+++ b/TestFiles/T1360.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; }
+td { border: thin solid blue; }
+th { border: thin solid aqua; }
+</style>
+</head>
+<body>
+<p>table-layout: auto</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 10em;'>Header 1 </th>
+ <td style='width: 15em;'>Cell 1 </td>
+ <td>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1370.html b/TestFiles/T1370.html
new file mode 100644
index 0000000..ce50aa2
--- /dev/null
+++ b/TestFiles/T1370.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; }
+td { border: thin solid blue; }
+th { border: thin solid aqua; }
+</style>
+</head>
+<body>
+<p>table-layout: auto</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1380.html b/TestFiles/T1380.html
new file mode 100644
index 0000000..924b210
--- /dev/null
+++ b/TestFiles/T1380.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; }
+</style>
+</head>
+<body>
+<p>table-layout: auto</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1390.html b/TestFiles/T1390.html
new file mode 100644
index 0000000..3ac0376
--- /dev/null
+++ b/TestFiles/T1390.html
@@ -0,0 +1,32 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; }
+tr#row3 { visibility: collapse }
+</style>
+</head>
+<body>
+<p>todo this doesn't work</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1400.html b/TestFiles/T1400.html
new file mode 100644
index 0000000..b98830a
--- /dev/null
+++ b/TestFiles/T1400.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; border-collapse: collapse; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; }
+</style>
+</head>
+<body>
+<p>todo this doesn't work</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1410.html b/TestFiles/T1410.html
new file mode 100644
index 0000000..de669b4
--- /dev/null
+++ b/TestFiles/T1410.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; border-collapse: separate; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; }
+</style>
+</head>
+<body>
+<p>todo this doesn't work</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1420.html b/TestFiles/T1420.html
new file mode 100644
index 0000000..12570a7
--- /dev/null
+++ b/TestFiles/T1420.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; border-collapse: separate; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; border-collapse: collapse; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; border-collapse: collapse; }
+</style>
+</head>
+<body>
+<p>todo this has different behavior between word and IE</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1430.html b/TestFiles/T1430.html
new file mode 100644
index 0000000..2e7791b
--- /dev/null
+++ b/TestFiles/T1430.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; border-collapse: separate; border-spacing: 2em; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; }
+</style>
+</head>
+<body>
+<p>todo works for chrome, not for ie / word</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1440.html b/TestFiles/T1440.html
new file mode 100644
index 0000000..12e1552
--- /dev/null
+++ b/TestFiles/T1440.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; border-collapse: separate; border-spacing: 2em; background: yellow; empty-cells: hide; }
+td { border: thin solid blue; vertical-align: middle; height: 50px; text-align: center; background: red; }
+th { border: thin solid aqua; vertical-align: middle; height: 50px; text-align: center; background: blue; }
+</style>
+</head>
+<body>
+<p>todo works for ie, not for word</p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td style='visibility: hidden;'></td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1450.html b/TestFiles/T1450.html
new file mode 100644
index 0000000..28b90ac
--- /dev/null
+++ b/TestFiles/T1450.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { border: medium solid red; border-collapse: collapse; background: yellow; }
+th { border: thin solid black; background: white; }
+td { border: thin solid blue; background: aqua; }
+</style>
+</head>
+<body>
+<p></p>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th style='width: 20%;'>Header 1 </th>
+ <td style='width: 40%;'>Cell 1 </td>
+ <td style='width: 40%;'>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td style='border-style: hidden;'>Cell 6</td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1460.html b/TestFiles/T1460.html
new file mode 100644
index 0000000..e6a6e6f
--- /dev/null
+++ b/TestFiles/T1460.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <LINK rel='stylesheet' href='test.css' type='text/css'/>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <div class='c1'>
+ <h1 style='color: blue'>Html Heading</h1>
+ <p>fzz</p>
+ <h2 class='c1' id='i01'>Html Heading</h2>
+ <p>fzz</p>
+ </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1470.html b/TestFiles/T1470.html
new file mode 100644
index 0000000..6ed8c00
--- /dev/null
+++ b/TestFiles/T1470.html
@@ -0,0 +1,31 @@
+
+<html>
+<head>
+<style type="text/css">
+table { table-layout: fixed }
+table { border: medium solid red; }
+td { border: thin solid blue; }
+th { border: thin solid aqua; }
+</style>
+</head>
+<body>
+<table>
+ <caption>This is a simple 3x3 table</caption>
+ <tr id="row1">
+ <th>Header 1 </th>
+ <td>Cell 1 </td>
+ <td>Cell 2</td>
+ </tr>
+ <tr id="row2">
+ <th>Header 2 </th>
+ <td>Cell 3 </td>
+ <td>Cell 4</td>
+ </tr>
+ <tr id="row3">
+ <th>Header 3 </th>
+ <td>Cell 5 </td>
+ <td>Cell 6 </td>
+ </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1480.html b/TestFiles/T1480.html
new file mode 100644
index 0000000..4e9ad0c
--- /dev/null
+++ b/TestFiles/T1480.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style type="text/css">
+</style>
+</head>
+<body>
+ <h1>Html Heading</h1>
+ <p>fzz</p>
+ <h2>Html Heading</h2>
+ <p>fzz</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1490.html b/TestFiles/T1490.html
new file mode 100644
index 0000000..1663fa2
--- /dev/null
+++ b/TestFiles/T1490.html
@@ -0,0 +1,14 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of positioning schemes</TITLE>
+</HEAD>
+<BODY>
+<P>Beginning of body contents.
+<SPAN id="outer"> Start of outer contents.
+<SPAN id="inner"> Inner contents.</SPAN>
+End of outer contents.</SPAN>
+End of body contents.
+</P>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1500.html b/TestFiles/T1500.html
new file mode 100644
index 0000000..2f36ee8
--- /dev/null
+++ b/TestFiles/T1500.html
@@ -0,0 +1,16 @@
+
+<HTML>
+<HEAD>
+<TITLE>Lowercase latin numbering</TITLE>
+<style type="text/css">
+ul { list-style-image: url("star.png") }
+</style>
+</HEAD>
+<BODY>
+<ol>
+<li> This is the first item.</li>
+<li> This is the second item.</li>
+<li> This is the third item.</li>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1510.html b/TestFiles/T1510.html
new file mode 100644
index 0000000..85fbd10
--- /dev/null
+++ b/TestFiles/T1510.html
@@ -0,0 +1,20 @@
+
+<HTML>
+<HEAD>
+<TITLE>Comparison of inside/outside position</TITLE>
+<style type="text/css">
+ul { list-style: outside }
+ul.compact { list-style: inside }
+</style>
+</HEAD>
+<BODY>
+<UL>
+<li>first list item comes first</li>
+<li>second list item comes second</li>
+</UL>
+<UL class="compact">
+<li>first list item comes first</li>
+<li>second list item comes second</li>
+</UL>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1520.html b/TestFiles/T1520.html
new file mode 100644
index 0000000..cbaf417
--- /dev/null
+++ b/TestFiles/T1520.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol.alpha li { list-style: lower-alpha }
+ul li { list-style: disc }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1530.html b/TestFiles/T1530.html
new file mode 100644
index 0000000..c921828
--- /dev/null
+++ b/TestFiles/T1530.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol li { list-style: lower-alpha }
+ul li { list-style: disc }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1540.html b/TestFiles/T1540.html
new file mode 100644
index 0000000..dd2e337
--- /dev/null
+++ b/TestFiles/T1540.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol li { list-style: decimal-leading-zero }
+ul li { list-style: circle }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1550.html b/TestFiles/T1550.html
new file mode 100644
index 0000000..bba6678
--- /dev/null
+++ b/TestFiles/T1550.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol li { list-style: upper-roman }
+ul li { list-style: square }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1560.html b/TestFiles/T1560.html
new file mode 100644
index 0000000..73b199e
--- /dev/null
+++ b/TestFiles/T1560.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol li { list-style: lower-latin }
+ul li { list-style: upper-latin }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1570.html b/TestFiles/T1570.html
new file mode 100644
index 0000000..5a8124b
--- /dev/null
+++ b/TestFiles/T1570.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol li { list-style: upper-alpha }
+ul li { list-style: lower-alpha }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1580.html b/TestFiles/T1580.html
new file mode 100644
index 0000000..160d699
--- /dev/null
+++ b/TestFiles/T1580.html
@@ -0,0 +1,17 @@
+<HTML>
+<HEAD>
+<TITLE>WARNING: Unexpected results due to cascade</TITLE>
+<style type="text/css">
+ol li { list-style: upper-roman }
+ul li { list-style: lower-greek }
+</style>
+</HEAD>
+<BODY>
+<ol class="alpha">
+<li>level 1</li>
+<UL>
+<li>level 2</li>
+</UL>
+</ol>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1590.html b/TestFiles/T1590.html
new file mode 100644
index 0000000..82f4460
--- /dev/null
+++ b/TestFiles/T1590.html
@@ -0,0 +1,43 @@
+<HTML>
+<HEAD>
+<TITLE>Test many border sizes</TITLE>
+<style type="text/css">
+</style>
+</HEAD>
+<BODY>
+<p style='border:solid black 0.5pt;'>border:solid black 0.5pt;</p>
+<p/>
+<p style='border:solid red 1.0pt;'>border:solid red 1.0pt;</p>
+<p/>
+<p style='border:solid green 2.0pt;'>border:solid green 2.0pt;</p>
+<p/>
+<p style='border:solid rgb(255,255,0) 4.0pt;'>border:solid rgb(255,255,0) 4.0pt;</p>
+<p/>
+<p style='border:solid black thin;'>border:solid black thin;</p>
+<p/>
+<p style='border:solid #F00 medium;'>border:solid #F00 medium;</p>
+<p/>
+<p style='border:solid #00FF00 thick;'>border:solid #00FF00 thick;</p>
+<p/>
+<p style='border:solid rgb(0%, 0%, 100%) 1px;'>border:solid rgb(0%, 0%, 100%) 1px;</p>
+<p/>
+<p style='border:solid black 2px;'>border:solid black 2px;</p>
+<p/>
+<p style='border:solid black 3px;'>border:solid black 3px;</p>
+<p/>
+<p style='border:solid black 4px;'>border:solid black 4px;</p>
+<p/>
+<p style='border:solid black 8px;'>border:solid black 8px;</p>
+<p/>
+<p style='border:solid black 1px;'>border:solid black 1px;</p>
+<p/>
+<p style='border:solid black 0.1in;'>border:solid black 0.1in;</p>
+<p/>
+<p style='border:solid black 0.25in;'>border:solid black 0.25in;</p>
+<p/>
+<p style='border:solid black 0.1cm;'>border:solid black 0.1cm;</p>
+<p/>
+<p style='border:solid black 1pc;'>border:solid black 1pc;</p>
+<p/>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1591.html b/TestFiles/T1591.html
new file mode 100644
index 0000000..3006657
--- /dev/null
+++ b/TestFiles/T1591.html
@@ -0,0 +1,19 @@
+<HTML>
+<HEAD>
+<TITLE>Test many border sizes</TITLE>
+<style type="text/css">
+</style>
+</HEAD>
+<BODY>
+<p style='border:solid black 0.1in;'>border:solid black 0.1in;</p>
+<p/>
+<p style='border:solid black 0.25in;'>border:solid black 0.25in;</p>
+<p/>
+<p style='border:solid black 0.1cm;'>border:solid black 0.1cm;</p>
+<p/>
+<p style='border:solid black 1pc;'>border:solid black 1pc;</p>
+<p/>
+<p style='border:solid black 12pt;'>border:solid black 12pt;</p>
+<p/>
+</BODY>
+</HTML>
\ No newline at end of file
diff --git a/TestFiles/T1610.html b/TestFiles/T1610.html
new file mode 100644
index 0000000..88bd21f
--- /dev/null
+++ b/TestFiles/T1610.html
@@ -0,0 +1,53 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=windows-1252" />
+<meta name="Generator" content="Microsoft Word 14 (filtered)" />
+<style>
+//<![CDATA[
+
+<!--
+ /* Font Definitions */
+ @font-face
+ {font-family:Calibri;
+ panose-1:2 15 5 2 2 2 4 3 2 4;}
+@font-face
+ {font-family:"Traditional Arabic";
+ panose-1:2 2 6 3 5 4 5 2 3 4;}
+ /* Style Definitions */
+ p.MsoNormal, li.MsoNormal, div.MsoNormal
+ {margin-top:0in;
+ margin-right:0in;
+ margin-bottom:10.0pt;
+ margin-left:0in;
+ line-height:115%;
+ font-size:11.0pt;
+ font-family:"Calibri","sans-serif";}
+.MsoChpDefault
+ {font-family:"Calibri","sans-serif";}
+.MsoPapDefault
+ {margin-bottom:10.0pt;
+ line-height:115%;}
+@page WordSection1
+ {size:8.5in 11.0in;
+ margin:1.0in 1.0in 1.0in 1.0in;}
+div.WordSection1
+ {page:WordSection1;}
+-->
+
+//]]>//
+</style>
+
+</head>
+
+<body lang="EN-US">
+
+<div class="WordSection1">
+
+<p class="MsoNormal" align="right" style='text-align:right'><b><span lang="AR-SA" dir="RTL" style='font-size:8.0pt;line-height:115%;font-family:"Traditional Arabic","serif"'>العلوم
+الاجتماعية</span></b></p>
+
+</div>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1620.html b/TestFiles/T1620.html
new file mode 100644
index 0000000..2425788
--- /dev/null
+++ b/TestFiles/T1620.html
@@ -0,0 +1,263 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 3.00in;
+ background: #C2D69B;
+ font-family: Bernard MT Condensed;
+ font-size: 26pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-family: Bernard MT Condensed;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000000 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 3.00in;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000001 {
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000002 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000003 {
+ color: #FFFFFF;
+ background: #1F497D;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+div.pt-000004 {
+ border-top: solid windowtext 1.0pt;
+ padding-top: 1.0pt;
+ border-right: solid windowtext 1.0pt;
+ padding-right: 4.0pt;
+ border-bottom: solid windowtext 1.0pt;
+ padding-bottom: 1.0pt;
+ border-left: solid windowtext 1.0pt;
+ padding-left: 4.0pt;
+ margin-left: 0.50in;
+}
+p.pt-Normal-000005 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 0;
+ background: #FFC000;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000006 {
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000007 {
+ border-collapse: collapse;
+ border: none;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 136.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+p.pt-Normal-000009 {
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-PlaceholderText {
+ color: black;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: top;
+ width: 118.3pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+p.pt-Normal-000011 {
+ margin-bottom: 0;
+ text-align: right;
+ font-family: Calibri;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+td.pt-000012 {
+ vertical-align: top;
+ width: 218.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+td.pt-000013 {
+ vertical-align: top;
+ width: 136.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+span.pt-DefaultParagraphFont-000014 {
+ color: #FFFFFF;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000015 {
+ vertical-align: top;
+ width: 118.3pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #B6DDE8;
+}
+td.pt-000016 {
+ vertical-align: top;
+ width: 218.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #B6DDE8;
+}
+span.pt-PlaceholderText-000017 {
+ color: black;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000018 {
+ line-height: 115.0%;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000019 {
+ color: #FFFFFF;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+div.pt-000020 {
+ border-top: none;
+ border-right: none;
+ border-bottom: solid windowtext 1.0pt;
+ padding-bottom: 1.0pt;
+ border-left: none;
+}
+p.pt-Normal-000021 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 2.50in;
+ font-family: Bernard MT Condensed;
+ font-size: 22pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000022 {
+ font-family: Bernard MT Condensed;
+ font-size: 22pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000023 {
+ font-size: 22pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont">Contoso, Inc</span></p><p dir="ltr" class="pt-Normal-000000"><span class="pt-DefaultParagraphFont-000001">January 31, 2011</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Dear Eric White,</span></p><p dir="ltr" class="pt-Normal-000002"><span xml:space="preserve" class="pt-DefaultParagraphFont-000001">On the Insert tab, </span><span class="pt-DefaultParagraphFont-000003">the galleries include items that are designed to coordinate</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000001"> with the overall look of your document.</span></p><div class="pt-000004"><p dir="ltr" class="pt-Normal-000005"><span class="pt-DefaultParagraphFont-000006">Important note: Your new customer ID: 1.</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">When you create pictures, charts, or diagrams, they also coordinate with your current document look.</span></p><div align="left"><table dir="ltr" class="pt-000007"><tr><td class="pt-000008"><p dir="ltr" class="pt-Normal-000009"><span class="pt-PlaceholderText">Description</span></p></td><td class="pt-000010"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText">Quantity</span></p></td><td class="pt-000012"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText">Order Date</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Unicycle</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">8</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Hang Glider</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">1</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Hang Glider</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">3</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Hang Glider</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">1</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr></table></div><p dir="ltr" class="pt-Normal-000018"><span xml:space="preserve" class="pt-000019"> </span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #1 is Unicycle.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 8</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #2 is Hang Glider.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 1</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #3 is Hang Glider.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 3</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #4 is Hang Glider.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 1</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">We really appreciate your business.</span></p><p dir="ltr" class="pt-Normal-000021"><span class="pt-DefaultParagraphFont-000022">Eric White</span><span class="pt-DefaultParagraphFont-000023"><br />‎</span><span class="pt-DefaultParagraphFont-000022">Director of Customer Relations</span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1630.html b/TestFiles/T1630.html
new file mode 100644
index 0000000..abd94a4
--- /dev/null
+++ b/TestFiles/T1630.html
@@ -0,0 +1,263 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 3.00in;
+ background: #C2D69B;
+ font-family: Bernard MT Condensed;
+ font-size: 26pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-family: Bernard MT Condensed;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000000 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 3.00in;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000001 {
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000002 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000003 {
+ color: #FFFFFF;
+ background: #1F497D;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+div.pt-000004 {
+ border-top: solid windowtext 1.0pt;
+ padding-top: 1.0pt;
+ border-right: solid windowtext 1.0pt;
+ padding-right: 4.0pt;
+ border-bottom: solid windowtext 1.0pt;
+ padding-bottom: 1.0pt;
+ border-left: solid windowtext 1.0pt;
+ padding-left: 4.0pt;
+ margin-left: 0.50in;
+}
+p.pt-Normal-000005 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 0;
+ background: #FFC000;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000006 {
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000007 {
+ border-collapse: collapse;
+ border: none;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 136.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+p.pt-Normal-000009 {
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-PlaceholderText {
+ color: black;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: top;
+ width: 118.3pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+p.pt-Normal-000011 {
+ margin-bottom: 0;
+ text-align: right;
+ font-family: Calibri;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+td.pt-000012 {
+ vertical-align: top;
+ width: 218.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+td.pt-000013 {
+ vertical-align: top;
+ width: 136.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+span.pt-DefaultParagraphFont-000014 {
+ color: #FFFFFF;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000015 {
+ vertical-align: top;
+ width: 118.3pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #B6DDE8;
+}
+td.pt-000016 {
+ vertical-align: top;
+ width: 218.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #B6DDE8;
+}
+span.pt-PlaceholderText-000017 {
+ color: black;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000018 {
+ line-height: 115.0%;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000019 {
+ color: #FFFFFF;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+div.pt-000020 {
+ border-top: none;
+ border-right: none;
+ border-bottom: solid windowtext 1.0pt;
+ padding-bottom: 1.0pt;
+ border-left: none;
+}
+p.pt-Normal-000021 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 2.50in;
+ font-family: Bernard MT Condensed;
+ font-size: 22pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000022 {
+ font-family: Bernard MT Condensed;
+ font-size: 22pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000023 {
+ font-size: 22pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div class="pt-000004"><p dir="ltr" class="pt-Normal-000005"><span class="pt-DefaultParagraphFont-000006">Important note: Your new customer ID: 1.</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">When you create pictures, charts, or diagrams, they also coordinate with your current document look.</span></p><div align="left"><table dir="ltr" class="pt-000007"><tr><td class="pt-000008"><p dir="ltr" class="pt-Normal-000009"><span class="pt-PlaceholderText">Description</span></p></td><td class="pt-000010"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText">Quantity</span></p></td><td class="pt-000012"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText">Order Date</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Unicycle</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">8</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Hang Glider</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">1</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Hang Glider</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">3</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr><tr><td class="pt-000013"><p dir="ltr" class="pt-Normal-000009"><span class="pt-DefaultParagraphFont-000014">Hang Glider</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Normal-000011"><span class="pt-DefaultParagraphFont-000001">1</span></p></td><td class="pt-000016"><p dir="ltr" class="pt-Normal-000011"><span class="pt-PlaceholderText-000017">September 26, 2015</span></p></td></tr></table></div><p dir="ltr" class="pt-Normal-000018"><span xml:space="preserve" class="pt-000019"> </span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #1 is Unicycle.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 8</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #2 is Hang Glider.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 1</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #3 is Hang Glider.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 3</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">The description for Order #4 is Hang Glider.</span></p><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Quantity: 1</span></p><div class="pt-000020"><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">Date: September 26, 2015</span></p></div><p dir="ltr" class="pt-Normal-000002"><span class="pt-DefaultParagraphFont-000001">We really appreciate your business.</span></p><p dir="ltr" class="pt-Normal-000021"><span class="pt-DefaultParagraphFont-000022">Eric White</span><span class="pt-DefaultParagraphFont-000023"><br />‎</span><span class="pt-DefaultParagraphFont-000022">Director of Customer Relations</span></p></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1640.html b/TestFiles/T1640.html
new file mode 100644
index 0000000..0a8114e
--- /dev/null
+++ b/TestFiles/T1640.html
@@ -0,0 +1,263 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 3.00in;
+ background: #C2D69B;
+ font-family: Bernard MT Condensed;
+ font-size: 26pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-family: Bernard MT Condensed;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000000 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 3.00in;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000001 {
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000002 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000003 {
+ color: #FFFFFF;
+ background: #1F497D;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+div.pt-000004 {
+ border-top: solid windowtext 1.0pt;
+ padding-top: 1.0pt;
+ border-right: solid windowtext 1.0pt;
+ padding-right: 4.0pt;
+ border-bottom: solid windowtext 1.0pt;
+ padding-bottom: 1.0pt;
+ border-left: solid windowtext 1.0pt;
+ padding-left: 4.0pt;
+ margin-left: 0.50in;
+}
+p.pt-Normal-000005 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 0;
+ background: #FFC000;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000006 {
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: italic;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000007 {
+ border-collapse: collapse;
+ border: none;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 136.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+p.pt-Normal-000009 {
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-PlaceholderText {
+ color: black;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: top;
+ width: 118.3pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+p.pt-Normal-000011 {
+ margin-bottom: 0;
+ text-align: right;
+ font-family: Calibri;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+td.pt-000012 {
+ vertical-align: top;
+ width: 218.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+td.pt-000013 {
+ vertical-align: top;
+ width: 136.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #4BACC6;
+}
+span.pt-DefaultParagraphFont-000014 {
+ color: #FFFFFF;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000015 {
+ vertical-align: top;
+ width: 118.3pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #B6DDE8;
+}
+td.pt-000016 {
+ vertical-align: top;
+ width: 218.55pt;
+ border-top: solid #FFFFFF 1.0pt;
+ padding-top: 0;
+ border-right: solid #FFFFFF 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #FFFFFF 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #FFFFFF 1.0pt;
+ padding-left: 5.4pt;
+ background: #B6DDE8;
+}
+span.pt-PlaceholderText-000017 {
+ color: black;
+ font-family: Calibri;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000018 {
+ line-height: 115.0%;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000019 {
+ color: #FFFFFF;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+div.pt-000020 {
+ border-top: none;
+ border-right: none;
+ border-bottom: solid windowtext 1.0pt;
+ padding-bottom: 1.0pt;
+ border-left: none;
+}
+p.pt-Normal-000021 {
+ line-height: 115.0%;
+ margin-bottom: 10pt;
+ margin-left: 2.50in;
+ font-family: Bernard MT Condensed;
+ font-size: 22pt;
+ margin-top: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000022 {
+ font-family: Bernard MT Condensed;
+ font-size: 22pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000023 {
+ font-size: 22pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><p dir="ltr" class="pt-Normal-000021"><span class="pt-DefaultParagraphFont-000022">Eric White</span><span class="pt-DefaultParagraphFont-000023"><br />‎</span><span class="pt-DefaultParagraphFont-000022">Director of Customer Relations</span></p></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1650.html b/TestFiles/T1650.html
new file mode 100644
index 0000000..2fd2b3a
--- /dev/null
+++ b/TestFiles/T1650.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p dir="ltr">This is a test.</p>
+<code>
+ if (element.Name == XhtmlNoNamespace.strong)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected));
+
+ if (element.Name == XhtmlNoNamespace.style)
+ return null;
+
+ if (element.Name == XhtmlNoNamespace.sub)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected));
+</code>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1660.html b/TestFiles/T1660.html
new file mode 100644
index 0000000..007046b
--- /dev/null
+++ b/TestFiles/T1660.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ .normal {
+ margin: 0pt;
+ }
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+<pre>
+ if (element.Name == XhtmlNoNamespace.strong)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected));
+
+ if (element.Name == XhtmlNoNamespace.style)
+ return null;
+
+ if (element.Name == XhtmlNoNamespace.sub)
+ return element.Nodes().Select(n => Transform(n, settings, wDoc, nextExpected));
+</pre>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1670.html b/TestFiles/T1670.html
new file mode 100644
index 0000000..f8580d7
--- /dev/null
+++ b/TestFiles/T1670.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ .normal {
+ margin: 0pt;
+ }
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+<blockquote cite="http://www.ericwhite.com">
+ Open XML is a technology that enables you to parse, query, create, and modify documents generated by Microsoft Office, as well as other conformant office suites.
+</blockquote>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1680.html b/TestFiles/T1680.html
new file mode 100644
index 0000000..6561b65
--- /dev/null
+++ b/TestFiles/T1680.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ .normal {
+ margin: 0pt;
+ }
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+<blockquote cite="http://www.ericwhite.com">
+ <p>Open XML is a technology that enables you to <em>parse, query, create, and modify documents</em> generated by Microsoft Office, as well as other conformant office suites.</p>
+</blockquote>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1690.html b/TestFiles/T1690.html
new file mode 100644
index 0000000..a152f5b
--- /dev/null
+++ b/TestFiles/T1690.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ blockquote {
+ margin: 4pt;
+ }
+ .normal {
+ margin: 0pt;
+ }
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+<blockquote cite="http://www.ericwhite.com">
+ <p>Open XML is a technology that enables you to <em>parse, query, create, and modify documents</em> generated by Microsoft Office, as well as other conformant office suites.</p>
+</blockquote>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1700.html b/TestFiles/T1700.html
new file mode 100644
index 0000000..69212fd
--- /dev/null
+++ b/TestFiles/T1700.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ blockquote {
+ margin: 4pt;
+ }
+ .normal {
+ margin: 0pt;
+ }
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+<article>
+ <p>Open XML is a technology that enables you to <em>parse, query, create, and modify documents</em> generated by Microsoft Office, as well as other conformant office suites.</p>
+</article>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1710.html b/TestFiles/T1710.html
new file mode 100644
index 0000000..28f904b
--- /dev/null
+++ b/TestFiles/T1710.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta charset="UTF-8" />
+ <title></title>
+ <meta name="Generator" content="PowerTools for Open XML" />
+ <style>
+ blockquote {
+ margin: 4pt;
+ }
+ .normal {
+ margin: 0pt;
+ }
+ span {
+ white-space: pre-wrap;
+ }
+ body {
+ margin: 1cm auto;
+ max-width: 20cm;
+ padding: 0;
+ }
+ </style>
+</head>
+<body>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <section>
+ <article>
+ <p>Open XML is a technology that enables you to <em>parse, query, create, and modify documents</em> generated by Microsoft Office, as well as other conformant office suites.</p>
+ </article>
+ </section>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+ <p class='normal'>This is a test.</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/TestFiles/T1800.html b/TestFiles/T1800.html
new file mode 100644
index 0000000..40c95fc
--- /dev/null
+++ b/TestFiles/T1800.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Title {
+ margin-bottom: 26pt;
+ font-family: Microsoft YaHei UI;
+ font-size: 33pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #FA5A00;
+ font-family: Microsoft YaHei UI;
+ font-size: 33pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ margin-bottom: .001pt;
+}
+td.pt-000001 {
+ vertical-align: top;
+ width: 53.85pt;
+ border-top: none;
+ border-right: solid #DADADA 2.3pt;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+h1.pt-Heading1 {
+ line-height: 90.0%;
+ font-family: Microsoft YaHei UI;
+ font-size: 26pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000002 {
+ color: #FA5A00;
+ font-family: Microsoft YaHei UI;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000003 {
+ vertical-align: top;
+ width: 320.45pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: solid #DADADA 2.3pt;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-Normal {
+ font-family: Microsoft YaHei UI;
+ font-size: 11pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000004 {
+ color: #404040;
+ font-family: Microsoft YaHei UI;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 133.15pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-DefaultParagraphFont-000006 {
+ color: #404040;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000007 {
+ height: 0.44in;
+}
+td.pt-000008 {
+ vertical-align: top;
+ width: 53.85pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-000009 {
+ color: #FA5A00;
+ font-size: 26pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: top;
+ width: 320.45pt;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-000011 {
+ color: #404040;
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-Title"><span lang="zh-CN" class="pt-DefaultParagraphFont">5 日游行程计划</span></p><div align="left"><table dir="ltr" class="pt-000000"><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">1 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">目的地</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:[</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">您要去哪儿</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">在哪里吃饭:[早餐吃什么?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">安排什么活动:[是否要买演出的票?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">待在哪里</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:[沙滩屋还是朋友的沙发?]</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">抵达方式:[飞机、火车还是 GPS 自驾?]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="data:image/jpeg;base64,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" style="width: 1.849306in; height: 1.249306in" alt="图片 4" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">2 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">[要替换任何占位符文本(例如此文本),只需选中一行或一段文本并开始键入。为达到最佳效果,请勿在您选中的字符左侧或右侧包含空格。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="data:image/jpeg;base64,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" style="width: 1.849306in; height: 1.249306in" alt="图片 5" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">3 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">[要将任何占位符照片替换为您自己的照片,请删除它,然后在功能区的“插入”选项卡上单击“图片”。]</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="data:image/jpeg;base64,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" style="width: 1.849306in; height: 1.249306in" alt="图片 6" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">4 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">目的地:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">在哪里吃饭:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">安排什么活动:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">待在哪里</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">抵达方式:</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="data:image/jpeg;base64,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" style="width: 1.849306in; height: 1.249306in" alt="图片 7" /></span></p></td></tr><tr class="pt-000007"><td class="pt-000008"><h1 dir="ltr" class="pt-Heading1"><span xml:space="preserve" class="pt-000009"> </span></h1></td><td class="pt-000010"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></td></tr><tr><td class="pt-000001"><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">第</span></h1><h1 dir="ltr" class="pt-Heading1"><span lang="zh-CN" class="pt-DefaultParagraphFont-000002">5 天</span></h1></td><td class="pt-000003"><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">目的地:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">在哪里吃饭:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">安排什么活动:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">待在哪里</span><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">:</span></p><p dir="ltr" class="pt-Normal"><span lang="zh-CN" class="pt-DefaultParagraphFont-000004">抵达方式:</span></p></td><td class="pt-000005"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000006"><img src="data:image/jpeg;base64,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" style="width: 1.849306in; height: 1.249306in" alt="图片 8" /></span></p></td></tr></table></div><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000011"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1810.html b/TestFiles/T1810.html
new file mode 100644
index 0000000..b1c730f
--- /dev/null
+++ b/TestFiles/T1810.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title> </title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ text-align: center;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000000 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000001 {
+ text-align: center;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000002 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000003 {
+ text-align: center;
+ font-family: David;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000004 {
+ font-family: David;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000005 {
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000006 {
+ font-family: David;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000007 {
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.500in;
+}
+span.pt-000008 {
+ margin: 0 0 0 0.50in;
+ padding: 0 0 0 0;
+}
+p.pt-Normal-000009 {
+ text-align: center;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000010 {
+ color: #000000;
+ font-size: 10pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000011 {
+ color: #000000;
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000012 {
+ color: #000000;
+ font-family: 'Arial', 'sans-serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000013 {
+ font-family: David;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000014 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div dir="rtl"><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-DefaultParagraphFont"><img src="data:image/png;base64,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" style="width: 1.84375in; height: 2.427083in" alt="Picture 1" /></span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000001">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000002">‏טיול ליפן אפריל 2014‏</span></p><p dir="rtl" class="pt-Normal-000003">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏שלום רב,‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בחרתם לצאת ולתור את "ארץ השמש העולה", הלא היא יפן.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מרגע נחיתתכם באי הונשו ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ שהוא האי הגדול מבין ארבעת איי יפן העיקריים, תבינו שיפן היא לא רק "סוני", "טיוטה", "מיצובישי" וכו' אלא מדינה מרתקת, יפה ומגוונת.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ההתרגשות לקראת הנסיעה הולכת וגוברת עם התקרב מועד היציאה, ויחד עם זאת ישנן שאלות שעליהן הנכם מחכים לקבל תשובות.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מקווה שדף מידע זה יהיה לכם לעזר. אם לאחר מפגש הקבוצה וקריאת המידע המובא כאן תתעוררנה שאלות, הרגישו חופשיים ליצור עמי קשר עוד בטרם היציאה לטיול. אשתדל לתת לכם את האינפורמציה הנדרשת.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏להלן מספרי הטלפון שלי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בית ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 09-7425733‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏נייד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 052-2231525 ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מזג אויר ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ אביבי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ (כדאי ורצוי לבדוק באינטרנט לפני היציאה לטיול)‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מטבע מקומי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ יין (‏</span><span class="pt-DefaultParagraphFont-000002">‎JPY‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ )‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏שער חליפין ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000007">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏1$= 102.3 יין‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span class="pt-DefaultParagraphFont"><span class="pt-000008"> </span></span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏1ש"ח=29.41 יין‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏(שער ההמרה נכון ל ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 9/4/2014) ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏הוצאה יומית לאדם ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ כ- 25$‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בבתי עסק ובבתי המלון ניתן להשתמש בכרטיסי אשראי.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏הפרשי השעות בין ישראל ליפן ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 6+ (יפן מקדימה את ישראל ב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ 6 שעות).‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תקשורת ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ הקידומת ליפן היא 81 ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏טלפון מיפן לארץ: קוד גישה בינלאומי; ‏</span><span class="pt-DefaultParagraphFont-000002">‎JDC 001;KDD 0041;JT 0061 ‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ +972 +קידומת האזור בלי "0"'‏</span><span class="pt-DefaultParagraphFont-000002">‎ ‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏+מספר הטלפון.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏לבעלי סמארט פון ממליצה להוריד את התוכנה ‏</span><span class="pt-DefaultParagraphFont-000002">‎BPHON‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ של בזק. (רק בעלי קו בזק בביתם יכולים להוריד תוכנה זו).‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ביפן ניתן להשתמש רק בפלפונים מהדור השלישי ומעלה.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תקשורת אינטרנטית ברוב בתי המלון.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="ltr" class="pt-Normal-000009"><span lang="he-IL" class="pt-DefaultParagraphFont-000007">‏ ‏</span><span class="pt-DefaultParagraphFont-000010"><span class="pt-000008"> </span></span><span class="pt-DefaultParagraphFont-000010"><img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/7gAOQWRvYmUAZAAAAAAB/+EAukV4aWYAAE1NACoAAAAIAAQBGgAFAAAAAQAAAD4BGwAFAAAAAQAAAEYBKAADAAAAAQACAACHaQAEAAAAAQAAAE4AAAAAAEgAAAABAAAASAAAAAEAAAABkoYABwAAAFAAAABgAAAAAFVOSUNPREUAAEYAaQBsAGUAIAB3AHIAaQB0AHQAZQBuACAAYgB5ACAAQQBkAG8AYgBlACAAUABoAG8AdABvAHMAaABvAHAAqAAgADUALgAwAAD/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t////2wBDAAoHBwcIBwoICAoPCggKDxINCgoNEhQQEBIQEBQRDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/2wBDAQsMDBUTFSIYGCIUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAChAO0DAREAAhEBAxEB/8QAHwAAAAcBAQEBAQAAAAAAAAAABAUDAgYBAAcICQoL/8QAtRAAAgEDAwIEAgYHAwQCBgJzAQIDEQQABSESMUFRBhNhInGBFDKRoQcVsUIjwVLR4TMWYvAkcoLxJUM0U5KismNzwjVEJ5OjszYXVGR0w9LiCCaDCQoYGYSURUaktFbTVSga8uPzxNTk9GV1hZWltcXV5fVmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9zhIWGh4iJiouMjY6PgpOUlZaXmJmam5ydnp+So6SlpqeoqaqrrK2ur6/8QAHwEAAgIDAQEBAQEAAAAAAAAAAQACAwQFBgcICQoL/8QAtREAAgIBAgMFBQQFBgQIAwNtAQACEQMEIRIxQQVRE2EiBnGBkTKhsfAUwdHhI0IVUmJy8TMkNEOCFpJTJaJjssIHc9I14kSDF1STCAkKGBkmNkUaJ2R0VTfyo7PDKCnT4/OElKS0xNTk9GV1hZWltcXV5fVGVmZ2hpamtsbW5vZHV2d3h5ent8fX5/c4SFhoeIiYqLjI2Oj4OUlZaXmJmam5ydnp+So6SlpqeoqaqrrK2ur6/90ABAAe/9oADAMBAAIRAxEAPwDs2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KrZJI40LyMERerMaAffirGdX85wW6MtkvNtx6z7KD4qvV8FpAYUfOWrG/E/wBbfkAVHSgBIbjwpw/ZwWyp/9Ds2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVTnnit4mllbii9TirCPMGtGerzNwt1P7qHx/ym/ysCQgdO8n6prRW4vXNlZNQqpFZHU/yp+x/rPjSbZYPJnl0aebAWg9NiGMtT6pcAqsnq/a5fE2NIt//9Hs2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KrZJEjQu5Cqu5JxViOu6uprLKeMSV9KPuf8o4LSAh/KujSalcfpjUY/9HQ/6HE3Rj/v2n8i/sYhJZxhYuxV/9Ls2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVZJJHEheRgqjqTirGtb1qMIXc8Yk+wndj4nBapPomiz+YbsahfArpsZ+CPceqR+yv/FYI+PAAz5M/VVRQqgKqiiqNgAOwyTBvFXYq/wD/0+zYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqh7u8htU5SGrH7KDqcVYprXmED7Zq3+64l6A5G0gIfSfLN9q8q3ur8orP7Udv0dwenL/faf8AD40m65M5jjSNFjjUIiiiqooAB4ZJiuxV2KuxV//U7NirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVaxVvFWsVbxV2KrXdI1LuwVR1YmgxVJ7zW9iLWgQD4pm/wCNBgtWLXeq3V5cm2sFae4k6sNzgZAMg0LypDZlbu/pcahXkCd0jP8AkfzN/l4QFJZHhYuxV2KuxV2Kv//V7NirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirWKqNxdw29DKSqn9qhpgtUDPqjR/GoEsJ35RmhA91OC00ll15juQjvaSRyBPtKykOvz3wcSRFJp9ceWst3MZO4Too/wBjjaaVbHStU1wByTaaeejkfE4/4rX/AI2xARyZfpmk2WlwCG1Sh/akbd2P+U2SAQSjcKHYq7FXYq7FXYq//9bs2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVo+3XFUJJqCwtwmjZXPT+Uj/W2wWqm2sWu68uEnYMOuNqgrjVigKzx+pGf20PXASmkrn1h1geW1m+sRA0eJ9yP8lsiSyEUmutREiLd2pMMimkkYPQ/xQ4CWYCXXN6WlWdPgZh8Sjp/lYE0yvyt5atZLePU7xfVeT4oYWHwqK/C5H7TNlkQ1yky8AAUGwHQZJg3irsVdirsVdirsVdir//X7NirsVdirsVdirsVdirsVdirsVU5Zool5SMFHviqDk1qzQ0HJvcD+pwWmlB/MNsp2ic/d/XG1pRk8zwggJC3uWIH3YOJPCqRarFPVqkgdSu0i+5H7a42xpVkubeSMR3nF4ZP7ucfZPsf5WxSxjXTNpzhmBltm6N3UZAmmyItJH1SWJ+SuXt22ZO3+sMbZcKG+tNHMzxn4ZBRx4jIp5r9PsL7UZ3hsojIW+23RFHizYQFJpmmleSLK3Cyag31mcb8BtGPan2n/wBllgi1GVsnVVVQqgKoFABsABkmDeKuxV2KuxV2KuxV2KuxV//Q7NirsVdirsVdirsVaLKOpA+eKqbXVsv2pUH0jFUPJqtoo+BjI1aAKP64LWkufXJJHdV+GMVCsOv+TgtlSXvJNJIju1TLWtT3GwpXI2mlItyrTFKhI2KoOV6b1wMlAX0kTAq1COhGC1pFJrVVYOP3cm08fb/jIn8rYbRwrje/W7SaymPMx7xuepXE7pAosW4uHMIBY1oqgVJwUztl2h+SHuY1udVLRI1Clsuz0/4sb9j/AFcmItcp9zNrW1t7SBLe2jEcSCiqMm1K2KuxV2KuxV2KuxV2KuxV2KuxV//R7NirsVdirsVdirWKrJYY5VKuPp7jFWN6rp0kLFozSvQ9j88gWYSmO7bkUf4XHUYLZUrRty2XsNlHX6Bih3rIeJkJ9DlxQ7F1B+0QvTFV81NiDWiKQRuafF1/l4/triqClnFN9m7jAmkvnnrgZUgJZD2wJQ7SsDhVVtr1llQk9ipw0lm3knTbN4JNTZA90ZGRGbfiFA+x4N8WTiGmZZbkmDeKuxV2KuxV2KuxV2KuxV2KuxV2Kv8A/9Ls2KuxV2KuxVokAVOwxVJdS81abZsYYj9auR1jjPwg/wCXJ9kYLVARea5jPF60Y4yMFZE6KGPXkfidlwWy4U/k4Tx0b4kOLFi2s6Y0bc12bqj+PscgQ2RKUw3bo9CSsi4LZEIj6xGI6r8IFCVAqWPf/V5ft4UIeO+aHl3DArQ+BxtlSCnuOT1B27nI2lCyTd64VpDs5Y74qiNP0jUNWkeKxjDtGOTlmCgA9N2yQCDKlS48reYrWQB7KRx2aKkgqf8AUrhpAkHoXlPTbrTtHWG7AWd3aRkBrxDUop/ytskGuRsp3hYuxV2KuxV2KuxV2KuxV2KuxV2KuxV//9Ps2KuxVpmVVLMQqjck7AYqxzVPOum2ZaK1BvJx/IaRg/5UvT/gcFpAYhqeu6vq5KzycLc9LeKqp/sv25P9lgtNKFnGEZfboOwwJtN3i+EMBuKH7t8illOnSsVCH7JFVOSDEhGXFok8TRyd+h8DhpjbA9YtZLa4ZWFHU/ePHKy3g2l31k0wJUnnrhVQebwxVSLV6/RhW1tTsACWJoqjcknoBhQXqvlfRv0TpixyUN1N+8uD/lEbR/8APPJhpJTnCh2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kv/1OzYqxvW/N8OnzyWltCbi5j2lZjwiQkV+Jj9v/YYLSAxO4vtY1ucRPI05atIE+CEDxKV+Kn/ABZilZdaPc2PEzqtG6FSCoPhgRa1YlA6Yqj7DSb69b/RoSUrQyt8KD/Zftf7HFWW2Pl6G3RTcP68g+0KUT7uuGltEywLEQYxROwHbBSqyyL6RdyFCj4mJoMUFIvMNrFe2X1uEhnQVDD9pMEgyiaYBMSrnIBvUS5OFFtHfCq6KKWeVIIUMk0h4xxqKsT7Yot6B5X8njT3F9qHGS8oPTi6rF9P7Uv+X+zkgGqUrZXkmLsVdirsVdirsVdirsVdirsVdirsVdirsVdir//V7Niry/zDK0urzllpwPpgH/I6n/gsCxa0vUH04TSxRrLNIFVVclRQEnZlDYEpgZdR8wQWsCwOs0MkjPxoISHA4Fj24fFhWt2Q6V5VtrdRJfUuJuyfsL/zX/ssVT9VVVCqAqjYAbAYUOJCgkmgHUnFWN635x0uw5QIRPMOwPwg/NftYCmmHXPmu/uXqAOA+yHHT5J9nIsqTHyzrczSS292/NHaor0BPX/Y4grKKUa3a/Vr6WIfZrVP9U7jI1u2ROyXYVTXRfLeo6u4aJfStR9q4cEL/wA8x/uxsNIJAei6RoGm6THS2jBmIAknbd2p7/s/6q5IBqJtM8KHYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq//W7NirG9f8rNf3H1u1ZFmYfvEeoDEbcgQDgpIKnpvk6NKSag/Nuvox7L/sm6tjS2yWOKOJBHEoRFFFVRQDChfiqSax5s0nSkPOQSzdBFGQd/8AKbouC0gPP9V816zq7MisYLU/sLtUf5/zYLZUkwiANSat3Y74pK7kAfbucCpzp1heIROYmSFwCsjCgPywUthX188jbud24cWPyOJTFPfLvkpAsd5qtHLAPHajoK7/AL7+b/UyQCJSZmiIihEUKiiiqBQAewGSa12KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kv//X7NirsVaxVA6nrNhpcRkupQppURg/EfoxV5/rXnfUNTrBYL6Vudq77/xfI2zEe9Af4Z1L0GvbgBnA5lWb4uPduI2WmKmSXsd6dB2wJRmm6PqGqzelZxcgPtSHZF/1nwoJZ7onknTdP4z3QF3djcMw+BT/AJEf/Gz4aY2meo6e06kxitR9nwp4YoY+PK95eXcDzARWsL1k5bMwBB4ouCmQLMskxdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVf/0OzYq7FXYq85/MCeKfUYbf0w0kAJLU3+LouApjzY7ZXK213DcMgZYm5cO3T/AI1wMrTSTXNZ1eO406yt2X6yAhkiDeoIwwZwq/8AFvHg7t+xhtiQE80XyEKrcaq3uLVOnykf/mnGlJZnBbwW0Sw28axRLsqIAAPuwoVcVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir//0ezYq7FXYqxPzb5Xnv5fr9iOVxQLLF05AfZK9uWBUq0zyFfSqsl7ItspNWiHxPT5j4FxpNs103SrHTIRDaRBB+0/VmPi7YUIzFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq/wD/0uzYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//0+zYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q==" style="width: 1.322917in; height: 0.4895833in" alt="ib2225" /></span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏חשמל ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏ הספק ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏ 100‏</span><span class="pt-DefaultParagraphFont-000012">‎V‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000011">‏ .כך נראה תקע יפאני ולו יש להתאים מעביר.‏</span><span class="pt-DefaultParagraphFont-000012">‎ ‎</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏לבוש וציוד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ בגדים מתאימים למזג האויר. נכון לימים אלה הטמפ' היא בסביבות 20+ מעלות במהלך היום.יש להביא מעיל ,צעיף,מטריה, נעלי הליכה נוחות, נעלים להחלפה.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏שימו לב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏ בביקור בהר קויה עשוי להיות קר יותר מאשר ביתר חלקי הטיול.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏קרם הגנה‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏משקפי קריאה רזרביים‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏משקפי שמש‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תרופות ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ לעזרה ראשונה, כמו קולדקס, סטופ איט, דקסמול ומעורר פעולת מעים.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏צילום של הדרכון ושל כרטיס הטיסה.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏באם יש לך ספור מעניין/מוסיקה טובה/ רעיון למשחק ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏ נחמד באם גם פריטים אלה יהיו בין הדברים שהנך מביא/ה לטיול.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏מזוודה ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ מזוודת המטען לא תעלה על ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏20 ק"ג‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ לנוסע.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏תיק יד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ (מזוודת יד) ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏ כדאי ורצוי לארוז חליפת בגדים ובגדים תחתונים;תרופות לשימוש יום יומי.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בתיק היד ניתן להכניס מיכלים שהתכולה שלהם לא עולה על 50 מ"ל. אנא הקפידו על גודל זה.‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏כמו כן יש להוציא כל מכשיר חד מתיק היד.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏מבקשת מכל חברי הקבוצה להגיע ביום רביעי ה- 16/4/2014 בשעה 09:30 לשער מס' 32 שבשדה התעופה. אודה לכולם באם תעמדו בלוח זמנים זה.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏רק לאחר שהודעתם לי על הגעתכם תתחילו בתהליך הצ'אק אין. את מזוודת המטען יש לשלח ישירות לטוקיו.‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏עלינו לזכור שהצלחת הטיול תלויה בכל אחד מאתנו. אחד הכללים החשבוים הוא עמידה בלוח זמנים !!!!‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000014">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000013">‏ברצוני לאחל לכם ולב"ב חג אביב צבעוני ושמח !!!‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏בברכה,‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000004">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000005">‏שרה פרי‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000006">‏<span class="pt-000000">‏ ‏</span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1820.html b/TestFiles/T1820.html
new file mode 100644
index 0000000..0be7f8e
--- /dev/null
+++ b/TestFiles/T1820.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title> </title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ text-align: center;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000000 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000001 {
+ text-align: center;
+ font-family: Guttman Yad-Brush;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000002 {
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000003 {
+ text-align: center;
+ font-family: David;
+ font-size: 16pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000004 {
+ font-family: David;
+ font-size: 16pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000005 {
+ text-align: center;
+ font-family: David;
+ font-size: 16pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000006 {
+ font-size: 16pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000007 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000008 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000009 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+span.pt-DefaultParagraphFont-000010 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-000011 {
+ margin-right: 0.50in;
+ text-indent: -0.25in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000012 {
+ font-family: Aharoni;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-DefaultParagraphFont-000013 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-000014 {
+ margin: 0 0 0 0.50in;
+ padding: 0 0 0 0;
+}
+p.pt-Normal-000015 {
+ margin-right: 0.02in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000016 {
+ margin-right: 0.02in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+span.pt-000017 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-000018 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+ display: inline-block;
+ text-indent: 0;
+ width: 0.250in;
+}
+span.pt-DefaultParagraphFont-000019 {
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: bold;
+ margin: 0;
+ padding: 0;
+}
+p.pt-Normal-000020 {
+ margin-right: 0.25in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000021 {
+ margin-left: 0.02in;
+ font-family: Aharoni;
+ font-size: 12pt;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+p.pt-Normal-000022 {
+ margin-left: 0.02in;
+ font-family: 'Times New Roman', 'serif';
+ font-size: 12pt;
+ line-height: 108%;
+ margin-top: 0;
+ margin-right: 0;
+ margin-bottom: .001pt;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div dir="rtl"><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‎ ‎</span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-DefaultParagraphFont"><img src="data:image/gif;base64,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" style="width: 2.15625in; height: 2.375in" alt="MMj02831010000[1]" /></span></p><p dir="rtl" class="pt-Normal">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000001">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000003">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000004">‏טיול להודו נפאל פברואר 2013 ‏</span></p><p dir="rtl" class="pt-Normal-000005">‏<span class="pt-000006">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏לנוסעים שלום,‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏מועד הטיול הולך ומתקרב. אתם עומדים לצאת לחבל ארץ בתוך תת היבשת ההודית. הטיול הנו טיול מרתק. טיול שיפעיל את כל החושים. זה לא עוד טיול, אלא חוויה שאי-אפשר לחזור ממנה אדישים. ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏מקום שמעורר מחשבות רבות. מעלה הרבה מאוד סימני שאלה ויחד עם זאת רבים מהנוסעים חוזרים הביתה עם תחושה שהם רוצים לחזור ולתור בה פעם נוספת. מקווה שאתם תשובו עם הרגשה דומה.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏להלן מספר עצות לנוסעים:‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏הכנות טרום נסיעה ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏כדאי לפנות ללשכת הבריאות הקרובה למקום מגוריכם, בכדי להתייעץ עם המחלקה לטיולים ולקבל הדרכה לגבי החיסונים הדרושים לפי מצב הבריאות שלכם.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ויזות,דרכונים,ביטוח ותיקי יד -‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏1.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏תוקף הדרכון לפחות חצי שנה.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏2.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ויזה להודו (כניסה כפולה). הויזה לנפאל נעשית בכניסה לנפאל, יש להצטייד בשתי תמונות פספורט להוצאת הויזה. ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏עלות ויזה הוא 25$ לנוסע. כ"א מהנוסעים ישלם סכום זה בכניסה.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏3.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ביטוח רפואי ומטען: כדאי לעשות ביטוח רפואי הכולל פינוי בהיטס ולא להסתפק בביטוח סטנדרטי של חברות כרטיסי האשראי.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏4.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏יש לצלם את העמודים הרלוונטים מהדרכון (פרטים וויזה) וכן צילום של כרטיסי הטיסה ולהטמינם במזוודה.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏5.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏בתיק היד/מזוודת היד רצוי להכניס חליפת בגדים להחלפה ותרופות.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏6.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏בתיקי היד יש לקחת מיכלים (שפורפרות ובקבוקים) המכילים עד 50 ממ"ל.‏</span></p><p dir="rtl" class="pt-000011">‏<span lang="he-IL" class="pt-000012">‏7.‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏משקל מזוודת המטען לא יעלה על 20 ק"ג.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏הטיסה מנתב"ג ב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 25/2/2013, מספר טיסה ‏</span><span class="pt-DefaultParagraphFont-000013">‎TK ‎</span><span class="pt-DefaultParagraphFont-000013">‎–‎</span><span class="pt-DefaultParagraphFont-000013">‎ 787 ‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏את המטען יש לשלוח ישירות למובאי. ‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏יש להגיע לש"ת 3 לפני שעת ההמראה. כלומר,בשעה 12:30. אמתין לכם בשער 32 (על יד הדלפק שמצויין כנקודת מפגש לקבוצות). מבקשת מכל הנוסעים להגיע לנקודת המפגש עוד טרם תחילת תהליך הצ'ק אין.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏כסף והוצאות ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏כדאי להצטייד בדולר אמריקאי רצוי שהשטרות יהיו חדשים, כמו כן מומלץ להצטייד בשטרות הקטנים מ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 100$.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ההוצאות היומיות השוטפות לאדם הן כ- 20$ (אנחנו מקבלים ארוחת בוקר וערב).‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ סכום זה איננו כולל קניות.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏בנוסף לסכום הנ"ל -‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏כל נוסע ישלם 25$ עבור ויזה לנפאל.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏כל נוסע ישלם 30$ מס נמל בקטמנדו.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏רצוי לשמור את אחת מקבלות המרת הכספים וזאת בכדי שאפשר יהיה להחליף את הכסף העודף עם סיום הטיול.‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏שערי ההמרה:‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL">#x200fכ- 53.9 רופי הודי ל #x200f</span><span lang="he-IL">#x200f–#x200f</span><span lang="he-IL">#x200f 1$.#x200f</span><span class="pt-DefaultParagraphFont"><span class="pt-000014"> </span></span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏14.66 רופי הודי = 1 ש"ח‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL">#x200fכ- 86.68 רופי נפאלי ל #x200f</span><span lang="he-IL">#x200f–#x200f</span><span lang="he-IL">#x200f 1$.#x200f</span><span class="pt-DefaultParagraphFont"><span class="pt-000014"> </span></span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏23.75 רופי נפלאי = 1 ₪‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏הפרשי זמנים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏שעון נפאל ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מקדים‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ את ישראל ב- 3 שעות ו- 45 דקות‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏שעון הודו ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מקדים‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ את שעון ישראל ב ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 3 שעות ו ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 30 דקות‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏תקשורת ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏אינטרנט‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏טלפונים ציבוריים‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏טלפונים סלולריים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ השימוש יקר.ישנם אזורים שבהם אין כיסוי לטלפונים.‏</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000017">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏לבעלי הסמארט פון ובעלי קו בזק בבית, ממליצה להוריד את האפליקציה של בזק שנקראת בי-פון - ‏</span><span class="pt-DefaultParagraphFont-000013">‎BPHONE‎</span></p><p dir="rtl" class="pt-000011">‏<span class="pt-000018">‎-‎</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏צלמו את רשימת בתי המלון ומספרי הטלפון שתקבלו והשא‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ירו את הרשימה למשפחה.‏</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000007">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏חשמל: 220 ‏</span><span class="pt-DefaultParagraphFont-000019">‎W‎</span></p><p dir="rtl" class="pt-Normal-000009">‏<span class="pt-000002">‎ ‎</span></p><p dir="rtl" class="pt-Normal-000020">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏תרופות והיגיינ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ה‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏למשתמשים בתרופות באופן קבוע יש לקחת אותן בתיק היד. רצוי לקחת תרופות לשימוש אישי כגון: אקומול, אקומול קולד, תרופה נגד הרעלת קיבה, וכדורים מעוררי קיבה, אגדים נדבקים.‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ממליצה לקחת מטליות לחות וג'ל לחיטוי הידיים.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏אוכל ומים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏אפשר להביא מעט חטיפים, פירות יבשים וכו' לנשנוש בין הארוחות. (אל תעמיסו הרבה מדי !!).‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏למעונייני‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ם‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ באוכל צמחוני בטיסות יש לעדכן מבעוד מועד את סוכן הנסיעות שאצלו רכשתם את הטיול.‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מים ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏יש לשתות מי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ פולארי‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ס‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏. בעת קניית בקבוק מים יש לבדוק שהפקק הוא מקורי. ניתן לרכוש מים בכל מקום. אין צורך לקחת מהארץ.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏ציוד אישי ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏–‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏נעלי הליכה, כובע משקפי שמש, משקפי ראיה רזרביים, קרם הגנה, מעיל, מטריה.‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏מבחינתלבוש כדאי להצטייד בלבוש הדומה לזה שאנו מתלבשים בימים אלה בארץ. הבוקר חמים ובערב עשוי להיות קריר/קר.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏נחמד יהיה באם תביאו עמכם מספר תקליטורי מוסיקה, ספור/משחק וכו'.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏הצלחת הטיול תלויה בכל אחד ואחת מאתנו. חלק מההצלחה הוא עמידה בלוחות זמנים וגילוי סבלנות וסובלנות . ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏חשוב להשאיר את טרדות היום יום בבית, להגיע לטיול עם לב פתוח, אוזן קשובה ועין פקוחה, בכדי שהטיול יהיה משמעותי וחוייתי.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000010">‏בתקווה שנצא בשלום ונשוב כולנו לשלום.‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000002">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏בברכה,‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000000">‏ ‏</span></p><p dir="rtl" class="pt-Normal-000015">‏<span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏שרה פרי‏</span></p><p dir="rtl" class="pt-Normal-000016">‏<span class="pt-000000">‏ ‏</span></p><p dir="ltr" class="pt-Normal-000021"><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏טלפונים: בבית ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 09-7425733 : נייד ‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏–‏</span><span lang="he-IL" class="pt-DefaultParagraphFont-000008">‏ 052-22315252‏</span></p><p dir="ltr" class="pt-Normal-000022"><span xml:space="preserve" class="pt-000000"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1830.html b/TestFiles/T1830.html
new file mode 100644
index 0000000..571a1c9
--- /dev/null
+++ b/TestFiles/T1830.html
@@ -0,0 +1,335 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-bottom: .001pt;
+}
+tr.pt-000001 {
+ height: 0.20in;
+}
+td.pt-000002 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #88C589;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-NoSpacing {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000003 {
+ color: #595959;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000004 {
+ vertical-align: top;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #88C589;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #FFFFFF;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-TableSpacing {
+ margin-top: 0;
+ line-height: 2.0pt;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 2pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000006 {
+ color: #595959;
+ font-size: 2pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000007 {
+ vertical-align: top;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #FFFFFF;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+tr.pt-000008 {
+ height: 1.60in;
+}
+td.pt-000009 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #D7EBD7;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #595959;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: bottom;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #D7EBD7;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-MonthYear {
+ margin-top: 2pt;
+ margin-bottom: 12pt;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 44pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-Month {
+ color: #234824;
+ font-family: Calibri;
+ font-size: 44pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000011 {
+ color: #356D36;
+ font-family: Calibri;
+ font-size: 36pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000012 {
+ height: 0.25in;
+}
+table.pt-000013 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+tr.pt-000014 {
+ height: 0.40in;
+}
+td.pt-000015 {
+ vertical-align: middle;
+ width: 14.3%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #479249;
+}
+p.pt-Days {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 14pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000016 {
+ color: #FFFFFF;
+ font-family: Calibri;
+ font-size: 14pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000017 {
+ height: 0.30in;
+}
+td.pt-000018 {
+ vertical-align: top;
+ width: 14.3%;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: solid #88C589 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #88C589 1.0pt;
+ padding-left: 5.4pt;
+}
+p.pt-Dates {
+ margin-top: 6pt;
+ margin-bottom: 2pt;
+ margin-right: 0.10in;
+ font-family: Calibri;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-left: 0;
+}
+span.pt-000019 {
+ color: #262626;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000020 {
+ color: #262626;
+ font-family: Calibri;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000021 {
+ height: 0.70in;
+}
+p.pt-Normal {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ font-family: Calibri;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000022 {
+ color: #595959;
+ font-family: Calibri;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000023 {
+ height: 1.00in;
+}
+td.pt-000024 {
+ vertical-align: top;
+ width: 32.4pt;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #88C589 1.0pt;
+ padding-left: 5.4pt;
+ background: white;
+}
+p.pt-NoteHeading {
+ margin-top: 0;
+ margin-bottom: 0;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 18pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000025 {
+ color: #479249;
+ font-family: Calibri;
+ font-size: 18pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000026 {
+ vertical-align: middle;
+ width: 482.4pt;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: solid #88C589 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: white;
+}
+p.pt-Notes {
+ margin-top: 3pt;
+ line-height: 115.0%;
+ margin-bottom: 3pt;
+ font-family: Calibri;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000027 {
+ color: #595959;
+ font-family: Calibri;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000001"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr><td class="pt-000005"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td><td class="pt-000007"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td></tr><tr class="pt-000008"><td class="pt-000009"><p dir="ltr" class="pt-NoSpacing"><span class="pt-DefaultParagraphFont"><img src="data:image/jpeg;base64,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" style="width: 2.333333in; height: 1.60071in" alt="Sample Photo" /></span></p></td><td class="pt-000010"><p dir="ltr" class="pt-MonthYear"><span class="pt-Month">January</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000011"> </span><span class="pt-DefaultParagraphFont-000011">2014</span></p></td></tr><tr><td class="pt-000005"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td><td class="pt-000007"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td></tr><tr class="pt-000012"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p><div align="left"><table dir="ltr" class="pt-000013"><tr class="pt-000014"><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Sun.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Mon.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Tue.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Wed.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Thu.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Fri.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Sat.</span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">1</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">2</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">3</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">4</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">5</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">6</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">7</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">8</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">9</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">10</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">11</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Little League</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Jane’s Birthday</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">12</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">13</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">14</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">15</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">16</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">17</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">18</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Parent Teacher Conference</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">19</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">20</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">21</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">22</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">23</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">24</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">25</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Run for Life 5K</span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">26</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">27</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">28</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">29</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">30</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">31</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000023"><td class="pt-000024"><p dir="ltr" class="pt-NoteHeading"><span class="pt-DefaultParagraphFont-000025">notes</span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Notes"><span xml:space="preserve" class="pt-DefaultParagraphFont-000027">To replace the picture with your own, right-click it and then click Change Picture. </span></p><p dir="ltr" class="pt-Notes"><span xml:space="preserve" class="pt-DefaultParagraphFont-000027">To select new dates or change the calendar color, click the Calendar tab on the ribbon. </span></p><p dir="ltr" class="pt-Notes"><span class="pt-DefaultParagraphFont-000027">To try out a different set of fonts or colors, on the Calendar tab or Design tab, click Fonts or Colors.</span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1840.html b/TestFiles/T1840.html
new file mode 100644
index 0000000..571a1c9
--- /dev/null
+++ b/TestFiles/T1840.html
@@ -0,0 +1,335 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+table.pt-000000 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-bottom: .001pt;
+}
+tr.pt-000001 {
+ height: 0.20in;
+}
+td.pt-000002 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #88C589;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-NoSpacing {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000003 {
+ color: #595959;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000004 {
+ vertical-align: top;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #88C589;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+td.pt-000005 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #FFFFFF;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-TableSpacing {
+ margin-top: 0;
+ line-height: 2.0pt;
+ margin-bottom: 0;
+ font-family: Calibri;
+ font-size: 2pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-000006 {
+ color: #595959;
+ font-size: 2pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000007 {
+ vertical-align: top;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #FFFFFF;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+tr.pt-000008 {
+ height: 1.60in;
+}
+td.pt-000009 {
+ vertical-align: top;
+ width: 35.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #D7EBD7;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+span.pt-DefaultParagraphFont {
+ color: #595959;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000010 {
+ vertical-align: bottom;
+ width: 64.5%;
+ border-top: none;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: none;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: #D7EBD7;
+ padding-top: 0;
+ padding-bottom: 0;
+}
+p.pt-MonthYear {
+ margin-top: 2pt;
+ margin-bottom: 12pt;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 44pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-Month {
+ color: #234824;
+ font-family: Calibri;
+ font-size: 44pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000011 {
+ color: #356D36;
+ font-family: Calibri;
+ font-size: 36pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000012 {
+ height: 0.25in;
+}
+table.pt-000013 {
+ border-collapse: collapse;
+ border: none;
+ width: 100%;
+ margin-left: 0;
+ margin-bottom: .001pt;
+}
+tr.pt-000014 {
+ height: 0.40in;
+}
+td.pt-000015 {
+ vertical-align: middle;
+ width: 14.3%;
+ border-top-style: none;
+ padding-top: 0;
+ border-right-style: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left-style: none;
+ padding-left: 5.4pt;
+ background: #479249;
+}
+p.pt-Days {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 14pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000016 {
+ color: #FFFFFF;
+ font-family: Calibri;
+ font-size: 14pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000017 {
+ height: 0.30in;
+}
+td.pt-000018 {
+ vertical-align: top;
+ width: 14.3%;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: solid #88C589 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #88C589 1.0pt;
+ padding-left: 5.4pt;
+}
+p.pt-Dates {
+ margin-top: 6pt;
+ margin-bottom: 2pt;
+ margin-right: 0.10in;
+ font-family: Calibri;
+ font-size: 12pt;
+ line-height: 108%;
+ margin-left: 0;
+}
+span.pt-000019 {
+ color: #262626;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+span.pt-DefaultParagraphFont-000020 {
+ color: #262626;
+ font-family: Calibri;
+ font-size: 12pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000021 {
+ height: 0.70in;
+}
+p.pt-Normal {
+ margin-top: 2pt;
+ margin-bottom: 2pt;
+ font-family: Calibri;
+ font-size: 8pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000022 {
+ color: #595959;
+ font-family: Calibri;
+ font-size: 8pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+tr.pt-000023 {
+ height: 1.00in;
+}
+td.pt-000024 {
+ vertical-align: top;
+ width: 32.4pt;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: none;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: solid #88C589 1.0pt;
+ padding-left: 5.4pt;
+ background: white;
+}
+p.pt-NoteHeading {
+ margin-top: 0;
+ margin-bottom: 0;
+ text-align: center;
+ font-family: Calibri;
+ font-size: 18pt;
+ line-height: 108%;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000025 {
+ color: #479249;
+ font-family: Calibri;
+ font-size: 18pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+td.pt-000026 {
+ vertical-align: middle;
+ width: 482.4pt;
+ border-top: solid #88C589 1.0pt;
+ padding-top: 0;
+ border-right: solid #88C589 1.0pt;
+ padding-right: 5.4pt;
+ border-bottom: solid #88C589 1.0pt;
+ padding-bottom: 0;
+ border-left: none;
+ padding-left: 5.4pt;
+ background: white;
+}
+p.pt-Notes {
+ margin-top: 3pt;
+ line-height: 115.0%;
+ margin-bottom: 3pt;
+ font-family: Calibri;
+ font-size: 9pt;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont-000027 {
+ color: #595959;
+ font-family: Calibri;
+ font-size: 9pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000001"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr><td class="pt-000005"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td><td class="pt-000007"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td></tr><tr class="pt-000008"><td class="pt-000009"><p dir="ltr" class="pt-NoSpacing"><span class="pt-DefaultParagraphFont"><img src="data:image/jpeg;base64,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" style="width: 2.333333in; height: 1.60071in" alt="Sample Photo" /></span></p></td><td class="pt-000010"><p dir="ltr" class="pt-MonthYear"><span class="pt-Month">January</span><span xml:space="preserve" class="pt-DefaultParagraphFont-000011"> </span><span class="pt-DefaultParagraphFont-000011">2014</span></p></td></tr><tr><td class="pt-000005"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td><td class="pt-000007"><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></td></tr><tr class="pt-000012"><td class="pt-000002"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000004"><p dir="ltr" class="pt-NoSpacing"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p><div align="left"><table dir="ltr" class="pt-000013"><tr class="pt-000014"><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Sun.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Mon.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Tue.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Wed.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Thu.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Fri.</span></p></td><td class="pt-000015"><p dir="ltr" class="pt-Days"><span class="pt-DefaultParagraphFont-000016">Sat.</span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">1</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">2</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">3</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">4</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">5</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">6</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">7</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">8</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">9</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">10</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">11</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Little League</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Jane’s Birthday</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">12</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">13</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">14</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">15</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">16</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">17</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">18</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Parent Teacher Conference</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">19</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">20</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">21</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">22</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">23</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">24</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">25</span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont-000022">Run for Life 5K</span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">26</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">27</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">28</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">29</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">30</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span class="pt-DefaultParagraphFont-000020">31</span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr><tr class="pt-000017"><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Dates"><span xml:space="preserve" class="pt-000019"> </span></p></td></tr><tr class="pt-000021"><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td><td class="pt-000018"><p dir="ltr" class="pt-Normal"><span xml:space="preserve" class="pt-000003"> </span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p><div align="left"><table dir="ltr" class="pt-000000"><tr class="pt-000023"><td class="pt-000024"><p dir="ltr" class="pt-NoteHeading"><span class="pt-DefaultParagraphFont-000025">notes</span></p></td><td class="pt-000026"><p dir="ltr" class="pt-Notes"><span xml:space="preserve" class="pt-DefaultParagraphFont-000027">To replace the picture with your own, right-click it and then click Change Picture. </span></p><p dir="ltr" class="pt-Notes"><span xml:space="preserve" class="pt-DefaultParagraphFont-000027">To select new dates or change the calendar color, click the Calendar tab on the ribbon. </span></p><p dir="ltr" class="pt-Notes"><span class="pt-DefaultParagraphFont-000027">To try out a different set of fonts or colors, on the Calendar tab or Design tab, click Fonts or Colors.</span></p></td></tr></table></div><p dir="ltr" class="pt-TableSpacing"><span xml:space="preserve" class="pt-000006"> </span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/T1850.html b/TestFiles/T1850.html
new file mode 100644
index 0000000..5904bb5
--- /dev/null
+++ b/TestFiles/T1850.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html ><html xmlns="http://www.w3.org/1999/xhtml"><head><meta charset="UTF-8" /><title></title><meta name="Generator" content="PowerTools for Open XML" /><style>span { white-space: pre-wrap; }
+p.pt-Normal {
+ line-height: 107.9%;
+ margin-bottom: 8pt;
+ font-family: Calibri;
+ font-size: 11pt;
+ margin-top: 0;
+ margin-left: 0;
+ margin-right: 0;
+}
+span.pt-DefaultParagraphFont {
+ font-size: 11pt;
+ font-style: normal;
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+}
+body { margin: 1cm auto; max-width: 20cm; padding: 0; }</style></head><body><div><p dir="ltr" class="pt-Normal"><span class="pt-DefaultParagraphFont"><a href="http://www.ericwhite.com/" xmlns=""><img src="data:image/png;base64,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" style="width: 0.8223316in; height: 0.6347824in" alt="Picture 1" xmlns="http://www.w3.org/1999/xhtml" /></a></span></p></div></body></html>
\ No newline at end of file
diff --git a/TestFiles/WC/WC001-Digits-Deleted-Paragraph.docx b/TestFiles/WC/WC001-Digits-Deleted-Paragraph.docx
new file mode 100644
index 0000000..5be18d9
--- /dev/null
+++ b/TestFiles/WC/WC001-Digits-Deleted-Paragraph.docx
Binary files differ
diff --git a/TestFiles/WC/WC001-Digits-Mod.docx b/TestFiles/WC/WC001-Digits-Mod.docx
new file mode 100644
index 0000000..a77a4fe
--- /dev/null
+++ b/TestFiles/WC/WC001-Digits-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC001-Digits.docx b/TestFiles/WC/WC001-Digits.docx
new file mode 100644
index 0000000..e1146f3
--- /dev/null
+++ b/TestFiles/WC/WC001-Digits.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-DeleteAtBeginning.docx b/TestFiles/WC/WC002-DeleteAtBeginning.docx
new file mode 100644
index 0000000..d1e3ac1
--- /dev/null
+++ b/TestFiles/WC/WC002-DeleteAtBeginning.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-DeleteAtEnd.docx b/TestFiles/WC/WC002-DeleteAtEnd.docx
new file mode 100644
index 0000000..7e95b61
--- /dev/null
+++ b/TestFiles/WC/WC002-DeleteAtEnd.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-DeleteInMiddle.docx b/TestFiles/WC/WC002-DeleteInMiddle.docx
new file mode 100644
index 0000000..94b33a3
--- /dev/null
+++ b/TestFiles/WC/WC002-DeleteInMiddle.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-DiffAtBeginning.docx b/TestFiles/WC/WC002-DiffAtBeginning.docx
new file mode 100644
index 0000000..3771ac9
--- /dev/null
+++ b/TestFiles/WC/WC002-DiffAtBeginning.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-DiffInMiddle.docx b/TestFiles/WC/WC002-DiffInMiddle.docx
new file mode 100644
index 0000000..78c728a
--- /dev/null
+++ b/TestFiles/WC/WC002-DiffInMiddle.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-InsertAtBeginning.docx b/TestFiles/WC/WC002-InsertAtBeginning.docx
new file mode 100644
index 0000000..61d7212
--- /dev/null
+++ b/TestFiles/WC/WC002-InsertAtBeginning.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-InsertAtEnd.docx b/TestFiles/WC/WC002-InsertAtEnd.docx
new file mode 100644
index 0000000..cf0bae6
--- /dev/null
+++ b/TestFiles/WC/WC002-InsertAtEnd.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-InsertInMiddle.docx b/TestFiles/WC/WC002-InsertInMiddle.docx
new file mode 100644
index 0000000..41ae54f
--- /dev/null
+++ b/TestFiles/WC/WC002-InsertInMiddle.docx
Binary files differ
diff --git a/TestFiles/WC/WC002-Unmodified.docx b/TestFiles/WC/WC002-Unmodified.docx
new file mode 100644
index 0000000..65c4c11
--- /dev/null
+++ b/TestFiles/WC/WC002-Unmodified.docx
Binary files differ
diff --git a/TestFiles/WC/WC004-Large-Mod.docx b/TestFiles/WC/WC004-Large-Mod.docx
new file mode 100644
index 0000000..f30ad71
--- /dev/null
+++ b/TestFiles/WC/WC004-Large-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC004-Large.docx b/TestFiles/WC/WC004-Large.docx
new file mode 100644
index 0000000..b48495f
--- /dev/null
+++ b/TestFiles/WC/WC004-Large.docx
Binary files differ
diff --git a/TestFiles/WC/WC006-Table-Delete-Contests-of-Row.docx b/TestFiles/WC/WC006-Table-Delete-Contests-of-Row.docx
new file mode 100644
index 0000000..e2fd5dc
--- /dev/null
+++ b/TestFiles/WC/WC006-Table-Delete-Contests-of-Row.docx
Binary files differ
diff --git a/TestFiles/WC/WC006-Table-Delete-Row.docx b/TestFiles/WC/WC006-Table-Delete-Row.docx
new file mode 100644
index 0000000..e3eb923
--- /dev/null
+++ b/TestFiles/WC/WC006-Table-Delete-Row.docx
Binary files differ
diff --git a/TestFiles/WC/WC006-Table.docx b/TestFiles/WC/WC006-Table.docx
new file mode 100644
index 0000000..507f864
--- /dev/null
+++ b/TestFiles/WC/WC006-Table.docx
Binary files differ
diff --git a/TestFiles/WC/WC007-Deleted-at-Beginning-of-Para.docx b/TestFiles/WC/WC007-Deleted-at-Beginning-of-Para.docx
new file mode 100644
index 0000000..f6b17e4
--- /dev/null
+++ b/TestFiles/WC/WC007-Deleted-at-Beginning-of-Para.docx
Binary files differ
diff --git a/TestFiles/WC/WC007-Longest-At-End.docx b/TestFiles/WC/WC007-Longest-At-End.docx
new file mode 100644
index 0000000..4e1df10
--- /dev/null
+++ b/TestFiles/WC/WC007-Longest-At-End.docx
Binary files differ
diff --git a/TestFiles/WC/WC007-Moved-into-Table.docx b/TestFiles/WC/WC007-Moved-into-Table.docx
new file mode 100644
index 0000000..b25b39a
--- /dev/null
+++ b/TestFiles/WC/WC007-Moved-into-Table.docx
Binary files differ
diff --git a/TestFiles/WC/WC007-Unmodified.docx b/TestFiles/WC/WC007-Unmodified.docx
new file mode 100644
index 0000000..c181049
--- /dev/null
+++ b/TestFiles/WC/WC007-Unmodified.docx
Binary files differ
diff --git a/TestFiles/WC/WC009-Table-Cell-1-1-Mod.docx b/TestFiles/WC/WC009-Table-Cell-1-1-Mod.docx
new file mode 100644
index 0000000..26d891e
--- /dev/null
+++ b/TestFiles/WC/WC009-Table-Cell-1-1-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC009-Table-Unmodified.docx b/TestFiles/WC/WC009-Table-Unmodified.docx
new file mode 100644
index 0000000..d258a2b
--- /dev/null
+++ b/TestFiles/WC/WC009-Table-Unmodified.docx
Binary files differ
diff --git a/TestFiles/WC/WC010-Para-Before-Table-Mod.docx b/TestFiles/WC/WC010-Para-Before-Table-Mod.docx
new file mode 100644
index 0000000..e9c986b
--- /dev/null
+++ b/TestFiles/WC/WC010-Para-Before-Table-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC010-Para-Before-Table-Unmodified.docx b/TestFiles/WC/WC010-Para-Before-Table-Unmodified.docx
new file mode 100644
index 0000000..d73ad2b
--- /dev/null
+++ b/TestFiles/WC/WC010-Para-Before-Table-Unmodified.docx
Binary files differ
diff --git a/TestFiles/WC/WC011-After.docx b/TestFiles/WC/WC011-After.docx
new file mode 100644
index 0000000..ff8b76f
--- /dev/null
+++ b/TestFiles/WC/WC011-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC011-Before.docx b/TestFiles/WC/WC011-Before.docx
new file mode 100644
index 0000000..d29a974
--- /dev/null
+++ b/TestFiles/WC/WC011-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC012-Math-After.docx b/TestFiles/WC/WC012-Math-After.docx
new file mode 100644
index 0000000..3ddeea8
--- /dev/null
+++ b/TestFiles/WC/WC012-Math-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC012-Math-Before.docx b/TestFiles/WC/WC012-Math-Before.docx
new file mode 100644
index 0000000..003559b
--- /dev/null
+++ b/TestFiles/WC/WC012-Math-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC013-Image-After.docx b/TestFiles/WC/WC013-Image-After.docx
new file mode 100644
index 0000000..91ef627
--- /dev/null
+++ b/TestFiles/WC/WC013-Image-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC013-Image-After2.docx b/TestFiles/WC/WC013-Image-After2.docx
new file mode 100644
index 0000000..b176b63
--- /dev/null
+++ b/TestFiles/WC/WC013-Image-After2.docx
Binary files differ
diff --git a/TestFiles/WC/WC013-Image-Before.docx b/TestFiles/WC/WC013-Image-Before.docx
new file mode 100644
index 0000000..a7917af
--- /dev/null
+++ b/TestFiles/WC/WC013-Image-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC013-Image-Before2.docx b/TestFiles/WC/WC013-Image-Before2.docx
new file mode 100644
index 0000000..32da26f
--- /dev/null
+++ b/TestFiles/WC/WC013-Image-Before2.docx
Binary files differ
diff --git a/TestFiles/WC/WC014-SmartArt-After.docx b/TestFiles/WC/WC014-SmartArt-After.docx
new file mode 100644
index 0000000..7f48a19
--- /dev/null
+++ b/TestFiles/WC/WC014-SmartArt-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC014-SmartArt-Before.docx b/TestFiles/WC/WC014-SmartArt-Before.docx
new file mode 100644
index 0000000..1e88c5d
--- /dev/null
+++ b/TestFiles/WC/WC014-SmartArt-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC014-SmartArt-With-Image-After.docx b/TestFiles/WC/WC014-SmartArt-With-Image-After.docx
new file mode 100644
index 0000000..a309d61
--- /dev/null
+++ b/TestFiles/WC/WC014-SmartArt-With-Image-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC014-SmartArt-With-Image-Before.docx b/TestFiles/WC/WC014-SmartArt-With-Image-Before.docx
new file mode 100644
index 0000000..e3a5078
--- /dev/null
+++ b/TestFiles/WC/WC014-SmartArt-With-Image-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC014-SmartArt-With-Image-Deleted-After.docx b/TestFiles/WC/WC014-SmartArt-With-Image-Deleted-After.docx
new file mode 100644
index 0000000..7318e2b
--- /dev/null
+++ b/TestFiles/WC/WC014-SmartArt-With-Image-Deleted-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC014-SmartArt-With-Image-Deleted-After2.docx b/TestFiles/WC/WC014-SmartArt-With-Image-Deleted-After2.docx
new file mode 100644
index 0000000..6cc8a6c
--- /dev/null
+++ b/TestFiles/WC/WC014-SmartArt-With-Image-Deleted-After2.docx
Binary files differ
diff --git a/TestFiles/WC/WC015-Three-Paragraphs-After.docx b/TestFiles/WC/WC015-Three-Paragraphs-After.docx
new file mode 100644
index 0000000..d6592e9
--- /dev/null
+++ b/TestFiles/WC/WC015-Three-Paragraphs-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC015-Three-Paragraphs.docx b/TestFiles/WC/WC015-Three-Paragraphs.docx
new file mode 100644
index 0000000..c809e64
--- /dev/null
+++ b/TestFiles/WC/WC015-Three-Paragraphs.docx
Binary files differ
diff --git a/TestFiles/WC/WC016-Para-Image-Para-w-Deleted-Image.docx b/TestFiles/WC/WC016-Para-Image-Para-w-Deleted-Image.docx
new file mode 100644
index 0000000..694de78
--- /dev/null
+++ b/TestFiles/WC/WC016-Para-Image-Para-w-Deleted-Image.docx
Binary files differ
diff --git a/TestFiles/WC/WC016-Para-Image-Para.docx b/TestFiles/WC/WC016-Para-Image-Para.docx
new file mode 100644
index 0000000..eafcb84
--- /dev/null
+++ b/TestFiles/WC/WC016-Para-Image-Para.docx
Binary files differ
diff --git a/TestFiles/WC/WC017-Image-After.docx b/TestFiles/WC/WC017-Image-After.docx
new file mode 100644
index 0000000..d4bcfd1
--- /dev/null
+++ b/TestFiles/WC/WC017-Image-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC017-Image.docx b/TestFiles/WC/WC017-Image.docx
new file mode 100644
index 0000000..70104ec
--- /dev/null
+++ b/TestFiles/WC/WC017-Image.docx
Binary files differ
diff --git a/TestFiles/WC/WC018-Field-Simple-After-1.docx b/TestFiles/WC/WC018-Field-Simple-After-1.docx
new file mode 100644
index 0000000..a129791
--- /dev/null
+++ b/TestFiles/WC/WC018-Field-Simple-After-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC018-Field-Simple-After-2.docx b/TestFiles/WC/WC018-Field-Simple-After-2.docx
new file mode 100644
index 0000000..780e0b8
--- /dev/null
+++ b/TestFiles/WC/WC018-Field-Simple-After-2.docx
Binary files differ
diff --git a/TestFiles/WC/WC018-Field-Simple-Before.docx b/TestFiles/WC/WC018-Field-Simple-Before.docx
new file mode 100644
index 0000000..307d9de
--- /dev/null
+++ b/TestFiles/WC/WC018-Field-Simple-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC019-Hyperlink-After-1.docx b/TestFiles/WC/WC019-Hyperlink-After-1.docx
new file mode 100644
index 0000000..afea956
--- /dev/null
+++ b/TestFiles/WC/WC019-Hyperlink-After-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC019-Hyperlink-After-2.docx b/TestFiles/WC/WC019-Hyperlink-After-2.docx
new file mode 100644
index 0000000..45966c8
--- /dev/null
+++ b/TestFiles/WC/WC019-Hyperlink-After-2.docx
Binary files differ
diff --git a/TestFiles/WC/WC019-Hyperlink-Before.docx b/TestFiles/WC/WC019-Hyperlink-Before.docx
new file mode 100644
index 0000000..77e66a9
--- /dev/null
+++ b/TestFiles/WC/WC019-Hyperlink-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC020-FootNote-After-1.docx b/TestFiles/WC/WC020-FootNote-After-1.docx
new file mode 100644
index 0000000..2fba493
--- /dev/null
+++ b/TestFiles/WC/WC020-FootNote-After-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC020-FootNote-After-2.docx b/TestFiles/WC/WC020-FootNote-After-2.docx
new file mode 100644
index 0000000..0ecb3e7
--- /dev/null
+++ b/TestFiles/WC/WC020-FootNote-After-2.docx
Binary files differ
diff --git a/TestFiles/WC/WC020-FootNote-Before.docx b/TestFiles/WC/WC020-FootNote-Before.docx
new file mode 100644
index 0000000..014d38f
--- /dev/null
+++ b/TestFiles/WC/WC020-FootNote-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC021-Math-After-1.docx b/TestFiles/WC/WC021-Math-After-1.docx
new file mode 100644
index 0000000..18791f7
--- /dev/null
+++ b/TestFiles/WC/WC021-Math-After-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC021-Math-After-2.docx b/TestFiles/WC/WC021-Math-After-2.docx
new file mode 100644
index 0000000..8c15166
--- /dev/null
+++ b/TestFiles/WC/WC021-Math-After-2.docx
Binary files differ
diff --git a/TestFiles/WC/WC021-Math-Before-1.docx b/TestFiles/WC/WC021-Math-Before-1.docx
new file mode 100644
index 0000000..40440ea
--- /dev/null
+++ b/TestFiles/WC/WC021-Math-Before-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC021-Math-Before-2.docx b/TestFiles/WC/WC021-Math-Before-2.docx
new file mode 100644
index 0000000..09294df
--- /dev/null
+++ b/TestFiles/WC/WC021-Math-Before-2.docx
Binary files differ
diff --git a/TestFiles/WC/WC022-Image-Math-Para-After.docx b/TestFiles/WC/WC022-Image-Math-Para-After.docx
new file mode 100644
index 0000000..cf1a703
--- /dev/null
+++ b/TestFiles/WC/WC022-Image-Math-Para-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC022-Image-Math-Para-Before.docx b/TestFiles/WC/WC022-Image-Math-Para-Before.docx
new file mode 100644
index 0000000..cc938b8
--- /dev/null
+++ b/TestFiles/WC/WC022-Image-Math-Para-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC023-Table-4-Row-Image-After-Delete-1-Row.docx b/TestFiles/WC/WC023-Table-4-Row-Image-After-Delete-1-Row.docx
new file mode 100644
index 0000000..b5b2f41
--- /dev/null
+++ b/TestFiles/WC/WC023-Table-4-Row-Image-After-Delete-1-Row.docx
Binary files differ
diff --git a/TestFiles/WC/WC023-Table-4-Row-Image-Before.docx b/TestFiles/WC/WC023-Table-4-Row-Image-Before.docx
new file mode 100644
index 0000000..1753b27
--- /dev/null
+++ b/TestFiles/WC/WC023-Table-4-Row-Image-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC024-Table-After.docx b/TestFiles/WC/WC024-Table-After.docx
new file mode 100644
index 0000000..5091460
--- /dev/null
+++ b/TestFiles/WC/WC024-Table-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC024-Table-After2.docx b/TestFiles/WC/WC024-Table-After2.docx
new file mode 100644
index 0000000..a33016b
--- /dev/null
+++ b/TestFiles/WC/WC024-Table-After2.docx
Binary files differ
diff --git a/TestFiles/WC/WC024-Table-Before.docx b/TestFiles/WC/WC024-Table-Before.docx
new file mode 100644
index 0000000..9558951
--- /dev/null
+++ b/TestFiles/WC/WC024-Table-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC025-Simple-Table-After.docx b/TestFiles/WC/WC025-Simple-Table-After.docx
new file mode 100644
index 0000000..ee83b21
--- /dev/null
+++ b/TestFiles/WC/WC025-Simple-Table-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC025-Simple-Table-Before.docx b/TestFiles/WC/WC025-Simple-Table-Before.docx
new file mode 100644
index 0000000..9bc1350
--- /dev/null
+++ b/TestFiles/WC/WC025-Simple-Table-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC026-Long-Table-After-1.docx b/TestFiles/WC/WC026-Long-Table-After-1.docx
new file mode 100644
index 0000000..6674fef
--- /dev/null
+++ b/TestFiles/WC/WC026-Long-Table-After-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC026-Long-Table-Before.docx b/TestFiles/WC/WC026-Long-Table-Before.docx
new file mode 100644
index 0000000..4746db6
--- /dev/null
+++ b/TestFiles/WC/WC026-Long-Table-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC027-Twenty-Paras-After-1.docx b/TestFiles/WC/WC027-Twenty-Paras-After-1.docx
new file mode 100644
index 0000000..e1af79f
--- /dev/null
+++ b/TestFiles/WC/WC027-Twenty-Paras-After-1.docx
Binary files differ
diff --git a/TestFiles/WC/WC027-Twenty-Paras-After-2.docx b/TestFiles/WC/WC027-Twenty-Paras-After-2.docx
new file mode 100644
index 0000000..1e7c5a0
--- /dev/null
+++ b/TestFiles/WC/WC027-Twenty-Paras-After-2.docx
Binary files differ
diff --git a/TestFiles/WC/WC027-Twenty-Paras-After-3.docx b/TestFiles/WC/WC027-Twenty-Paras-After-3.docx
new file mode 100644
index 0000000..402ad85
--- /dev/null
+++ b/TestFiles/WC/WC027-Twenty-Paras-After-3.docx
Binary files differ
diff --git a/TestFiles/WC/WC027-Twenty-Paras-Before.docx b/TestFiles/WC/WC027-Twenty-Paras-Before.docx
new file mode 100644
index 0000000..9c953a0
--- /dev/null
+++ b/TestFiles/WC/WC027-Twenty-Paras-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC030-Image-Math-After.docx b/TestFiles/WC/WC030-Image-Math-After.docx
new file mode 100644
index 0000000..97d24ae
--- /dev/null
+++ b/TestFiles/WC/WC030-Image-Math-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC030-Image-Math-Before.docx b/TestFiles/WC/WC030-Image-Math-Before.docx
new file mode 100644
index 0000000..4270247
--- /dev/null
+++ b/TestFiles/WC/WC030-Image-Math-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC031-Two-Maths-After.docx b/TestFiles/WC/WC031-Two-Maths-After.docx
new file mode 100644
index 0000000..bc75c6c
--- /dev/null
+++ b/TestFiles/WC/WC031-Two-Maths-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC031-Two-Maths-Before.docx b/TestFiles/WC/WC031-Two-Maths-Before.docx
new file mode 100644
index 0000000..52912b1
--- /dev/null
+++ b/TestFiles/WC/WC031-Two-Maths-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC032-Para-with-Para-Props-After.docx b/TestFiles/WC/WC032-Para-with-Para-Props-After.docx
new file mode 100644
index 0000000..5830d9d
--- /dev/null
+++ b/TestFiles/WC/WC032-Para-with-Para-Props-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC032-Para-with-Para-Props.docx b/TestFiles/WC/WC032-Para-with-Para-Props.docx
new file mode 100644
index 0000000..557bad4
--- /dev/null
+++ b/TestFiles/WC/WC032-Para-with-Para-Props.docx
Binary files differ
diff --git a/TestFiles/WC/WC033-Merged-Cells-After1.docx b/TestFiles/WC/WC033-Merged-Cells-After1.docx
new file mode 100644
index 0000000..78d0c9d
--- /dev/null
+++ b/TestFiles/WC/WC033-Merged-Cells-After1.docx
Binary files differ
diff --git a/TestFiles/WC/WC033-Merged-Cells-After2.docx b/TestFiles/WC/WC033-Merged-Cells-After2.docx
new file mode 100644
index 0000000..958338b
--- /dev/null
+++ b/TestFiles/WC/WC033-Merged-Cells-After2.docx
Binary files differ
diff --git a/TestFiles/WC/WC033-Merged-Cells-Before.docx b/TestFiles/WC/WC033-Merged-Cells-Before.docx
new file mode 100644
index 0000000..35469b8
--- /dev/null
+++ b/TestFiles/WC/WC033-Merged-Cells-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Endnotes-After1.docx b/TestFiles/WC/WC034-Endnotes-After1.docx
new file mode 100644
index 0000000..0351ba7
--- /dev/null
+++ b/TestFiles/WC/WC034-Endnotes-After1.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Endnotes-After2.docx b/TestFiles/WC/WC034-Endnotes-After2.docx
new file mode 100644
index 0000000..fdbaed5
--- /dev/null
+++ b/TestFiles/WC/WC034-Endnotes-After2.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Endnotes-After3.docx b/TestFiles/WC/WC034-Endnotes-After3.docx
new file mode 100644
index 0000000..0ff6ac2
--- /dev/null
+++ b/TestFiles/WC/WC034-Endnotes-After3.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Endnotes-Before.docx b/TestFiles/WC/WC034-Endnotes-Before.docx
new file mode 100644
index 0000000..52f5169
--- /dev/null
+++ b/TestFiles/WC/WC034-Endnotes-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Footnotes-After1.docx b/TestFiles/WC/WC034-Footnotes-After1.docx
new file mode 100644
index 0000000..9b822a2
--- /dev/null
+++ b/TestFiles/WC/WC034-Footnotes-After1.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Footnotes-After2.docx b/TestFiles/WC/WC034-Footnotes-After2.docx
new file mode 100644
index 0000000..bebec6d
--- /dev/null
+++ b/TestFiles/WC/WC034-Footnotes-After2.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Footnotes-After3.docx b/TestFiles/WC/WC034-Footnotes-After3.docx
new file mode 100644
index 0000000..dc9e0b5
--- /dev/null
+++ b/TestFiles/WC/WC034-Footnotes-After3.docx
Binary files differ
diff --git a/TestFiles/WC/WC034-Footnotes-Before.docx b/TestFiles/WC/WC034-Footnotes-Before.docx
new file mode 100644
index 0000000..93ebbfa
--- /dev/null
+++ b/TestFiles/WC/WC034-Footnotes-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC035-Endnote-After.docx b/TestFiles/WC/WC035-Endnote-After.docx
new file mode 100644
index 0000000..95cf7c9
--- /dev/null
+++ b/TestFiles/WC/WC035-Endnote-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC035-Endnote-Before.docx b/TestFiles/WC/WC035-Endnote-Before.docx
new file mode 100644
index 0000000..92dbdf3
--- /dev/null
+++ b/TestFiles/WC/WC035-Endnote-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC035-Footnote-After.docx b/TestFiles/WC/WC035-Footnote-After.docx
new file mode 100644
index 0000000..ea790a0
--- /dev/null
+++ b/TestFiles/WC/WC035-Footnote-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC035-Footnote-Before.docx b/TestFiles/WC/WC035-Footnote-Before.docx
new file mode 100644
index 0000000..92dbdf3
--- /dev/null
+++ b/TestFiles/WC/WC035-Footnote-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC036-Endnote-With-Table-After.docx b/TestFiles/WC/WC036-Endnote-With-Table-After.docx
new file mode 100644
index 0000000..92dcc44
--- /dev/null
+++ b/TestFiles/WC/WC036-Endnote-With-Table-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC036-Endnote-With-Table-Before.docx b/TestFiles/WC/WC036-Endnote-With-Table-Before.docx
new file mode 100644
index 0000000..18dc4e9
--- /dev/null
+++ b/TestFiles/WC/WC036-Endnote-With-Table-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC036-Footnote-With-Table-After.docx b/TestFiles/WC/WC036-Footnote-With-Table-After.docx
new file mode 100644
index 0000000..4d15f83
--- /dev/null
+++ b/TestFiles/WC/WC036-Footnote-With-Table-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC036-Footnote-With-Table-Before.docx b/TestFiles/WC/WC036-Footnote-With-Table-Before.docx
new file mode 100644
index 0000000..ca9965f
--- /dev/null
+++ b/TestFiles/WC/WC036-Footnote-With-Table-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC037-Textbox-After1.docx b/TestFiles/WC/WC037-Textbox-After1.docx
new file mode 100644
index 0000000..6bcbfcb
--- /dev/null
+++ b/TestFiles/WC/WC037-Textbox-After1.docx
Binary files differ
diff --git a/TestFiles/WC/WC037-Textbox-Before.docx b/TestFiles/WC/WC037-Textbox-Before.docx
new file mode 100644
index 0000000..03305b8
--- /dev/null
+++ b/TestFiles/WC/WC037-Textbox-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC038-Document-With-BR-After.docx b/TestFiles/WC/WC038-Document-With-BR-After.docx
new file mode 100644
index 0000000..87fa35d
--- /dev/null
+++ b/TestFiles/WC/WC038-Document-With-BR-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC038-Document-With-BR-Before.docx b/TestFiles/WC/WC038-Document-With-BR-Before.docx
new file mode 100644
index 0000000..6b3175f
--- /dev/null
+++ b/TestFiles/WC/WC038-Document-With-BR-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC039-Break-In-Row-After1.docx b/TestFiles/WC/WC039-Break-In-Row-After1.docx
new file mode 100644
index 0000000..4a814ff
--- /dev/null
+++ b/TestFiles/WC/WC039-Break-In-Row-After1.docx
Binary files differ
diff --git a/TestFiles/WC/WC039-Break-In-Row.docx b/TestFiles/WC/WC039-Break-In-Row.docx
new file mode 100644
index 0000000..59ecdd3
--- /dev/null
+++ b/TestFiles/WC/WC039-Break-In-Row.docx
Binary files differ
diff --git a/TestFiles/WC/WC040-Case-After.docx b/TestFiles/WC/WC040-Case-After.docx
new file mode 100644
index 0000000..df91450
--- /dev/null
+++ b/TestFiles/WC/WC040-Case-After.docx
Binary files differ
diff --git a/TestFiles/WC/WC040-Case-Before.docx b/TestFiles/WC/WC040-Case-Before.docx
new file mode 100644
index 0000000..8ef36e7
--- /dev/null
+++ b/TestFiles/WC/WC040-Case-Before.docx
Binary files differ
diff --git a/TestFiles/WC/WC041-Table-5-Mod.docx b/TestFiles/WC/WC041-Table-5-Mod.docx
new file mode 100644
index 0000000..0ef11d4
--- /dev/null
+++ b/TestFiles/WC/WC041-Table-5-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC041-Table-5.docx b/TestFiles/WC/WC041-Table-5.docx
new file mode 100644
index 0000000..ac50fe4
--- /dev/null
+++ b/TestFiles/WC/WC041-Table-5.docx
Binary files differ
diff --git a/TestFiles/WC/WC042-Table-5-Mod.docx b/TestFiles/WC/WC042-Table-5-Mod.docx
new file mode 100644
index 0000000..434f972
--- /dev/null
+++ b/TestFiles/WC/WC042-Table-5-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC042-Table-5.docx b/TestFiles/WC/WC042-Table-5.docx
new file mode 100644
index 0000000..8d3f8a7
--- /dev/null
+++ b/TestFiles/WC/WC042-Table-5.docx
Binary files differ
diff --git a/TestFiles/WC/WC043-Nested-Table-Mod.docx b/TestFiles/WC/WC043-Nested-Table-Mod.docx
new file mode 100644
index 0000000..870d714
--- /dev/null
+++ b/TestFiles/WC/WC043-Nested-Table-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC043-Nested-Table.docx b/TestFiles/WC/WC043-Nested-Table.docx
new file mode 100644
index 0000000..fe8253e
--- /dev/null
+++ b/TestFiles/WC/WC043-Nested-Table.docx
Binary files differ
diff --git a/TestFiles/WC/WC044-Text-Box-Mod.docx b/TestFiles/WC/WC044-Text-Box-Mod.docx
new file mode 100644
index 0000000..54f2190
--- /dev/null
+++ b/TestFiles/WC/WC044-Text-Box-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC044-Text-Box.docx b/TestFiles/WC/WC044-Text-Box.docx
new file mode 100644
index 0000000..932ecb9
--- /dev/null
+++ b/TestFiles/WC/WC044-Text-Box.docx
Binary files differ
diff --git a/TestFiles/WC/WC045-Text-Box-Mod.docx b/TestFiles/WC/WC045-Text-Box-Mod.docx
new file mode 100644
index 0000000..f494a0a
--- /dev/null
+++ b/TestFiles/WC/WC045-Text-Box-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC045-Text-Box.docx b/TestFiles/WC/WC045-Text-Box.docx
new file mode 100644
index 0000000..a2a09c1
--- /dev/null
+++ b/TestFiles/WC/WC045-Text-Box.docx
Binary files differ
diff --git a/TestFiles/WC/WC046-Two-Text-Box-Mod.docx b/TestFiles/WC/WC046-Two-Text-Box-Mod.docx
new file mode 100644
index 0000000..2b0d00d
--- /dev/null
+++ b/TestFiles/WC/WC046-Two-Text-Box-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC046-Two-Text-Box.docx b/TestFiles/WC/WC046-Two-Text-Box.docx
new file mode 100644
index 0000000..da00a49
--- /dev/null
+++ b/TestFiles/WC/WC046-Two-Text-Box.docx
Binary files differ
diff --git a/TestFiles/WC/WC047-Two-Text-Box-Mod.docx b/TestFiles/WC/WC047-Two-Text-Box-Mod.docx
new file mode 100644
index 0000000..0ca112f
--- /dev/null
+++ b/TestFiles/WC/WC047-Two-Text-Box-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC047-Two-Text-Box.docx b/TestFiles/WC/WC047-Two-Text-Box.docx
new file mode 100644
index 0000000..040561d
--- /dev/null
+++ b/TestFiles/WC/WC047-Two-Text-Box.docx
Binary files differ
diff --git a/TestFiles/WC/WC048-Text-Box-in-Cell-Mod.docx b/TestFiles/WC/WC048-Text-Box-in-Cell-Mod.docx
new file mode 100644
index 0000000..6c7db97
--- /dev/null
+++ b/TestFiles/WC/WC048-Text-Box-in-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC048-Text-Box-in-Cell.docx b/TestFiles/WC/WC048-Text-Box-in-Cell.docx
new file mode 100644
index 0000000..3f9ac7a
--- /dev/null
+++ b/TestFiles/WC/WC048-Text-Box-in-Cell.docx
Binary files differ
diff --git a/TestFiles/WC/WC049-Text-Box-in-Cell-Mod.docx b/TestFiles/WC/WC049-Text-Box-in-Cell-Mod.docx
new file mode 100644
index 0000000..9cf736b
--- /dev/null
+++ b/TestFiles/WC/WC049-Text-Box-in-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC049-Text-Box-in-Cell.docx b/TestFiles/WC/WC049-Text-Box-in-Cell.docx
new file mode 100644
index 0000000..b020062
--- /dev/null
+++ b/TestFiles/WC/WC049-Text-Box-in-Cell.docx
Binary files differ
diff --git a/TestFiles/WC/WC050-Table-in-Text-Box-Mod.docx b/TestFiles/WC/WC050-Table-in-Text-Box-Mod.docx
new file mode 100644
index 0000000..240b4ad
--- /dev/null
+++ b/TestFiles/WC/WC050-Table-in-Text-Box-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC050-Table-in-Text-Box.docx b/TestFiles/WC/WC050-Table-in-Text-Box.docx
new file mode 100644
index 0000000..b40e293
--- /dev/null
+++ b/TestFiles/WC/WC050-Table-in-Text-Box.docx
Binary files differ
diff --git a/TestFiles/WC/WC051-Table-in-Text-Box-Mod.docx b/TestFiles/WC/WC051-Table-in-Text-Box-Mod.docx
new file mode 100644
index 0000000..e659aa2
--- /dev/null
+++ b/TestFiles/WC/WC051-Table-in-Text-Box-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC051-Table-in-Text-Box.docx b/TestFiles/WC/WC051-Table-in-Text-Box.docx
new file mode 100644
index 0000000..fd72bbb
--- /dev/null
+++ b/TestFiles/WC/WC051-Table-in-Text-Box.docx
Binary files differ
diff --git a/TestFiles/WC/WC052-SmartArt-Same-Mod.docx b/TestFiles/WC/WC052-SmartArt-Same-Mod.docx
new file mode 100644
index 0000000..559fe21
--- /dev/null
+++ b/TestFiles/WC/WC052-SmartArt-Same-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC052-SmartArt-Same.docx b/TestFiles/WC/WC052-SmartArt-Same.docx
new file mode 100644
index 0000000..a309d61
--- /dev/null
+++ b/TestFiles/WC/WC052-SmartArt-Same.docx
Binary files differ
diff --git a/TestFiles/WC/WC053-Text-in-Cell-Mod.docx b/TestFiles/WC/WC053-Text-in-Cell-Mod.docx
new file mode 100644
index 0000000..25b6e2c
--- /dev/null
+++ b/TestFiles/WC/WC053-Text-in-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC053-Text-in-Cell.docx b/TestFiles/WC/WC053-Text-in-Cell.docx
new file mode 100644
index 0000000..3f01db8
--- /dev/null
+++ b/TestFiles/WC/WC053-Text-in-Cell.docx
Binary files differ
diff --git a/TestFiles/WC/WC054-Text-in-Cell-Mod.docx b/TestFiles/WC/WC054-Text-in-Cell-Mod.docx
new file mode 100644
index 0000000..25b6e2c
--- /dev/null
+++ b/TestFiles/WC/WC054-Text-in-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC054-Text-in-Cell.docx b/TestFiles/WC/WC054-Text-in-Cell.docx
new file mode 100644
index 0000000..00933df
--- /dev/null
+++ b/TestFiles/WC/WC054-Text-in-Cell.docx
Binary files differ
diff --git a/TestFiles/WC/WC055-French-Mod.docx b/TestFiles/WC/WC055-French-Mod.docx
new file mode 100644
index 0000000..d996872
--- /dev/null
+++ b/TestFiles/WC/WC055-French-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC055-French.docx b/TestFiles/WC/WC055-French.docx
new file mode 100644
index 0000000..67eec40
--- /dev/null
+++ b/TestFiles/WC/WC055-French.docx
Binary files differ
diff --git a/TestFiles/WC/WC056-French-Mod.docx b/TestFiles/WC/WC056-French-Mod.docx
new file mode 100644
index 0000000..1e8d6c0
--- /dev/null
+++ b/TestFiles/WC/WC056-French-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC056-French.docx b/TestFiles/WC/WC056-French.docx
new file mode 100644
index 0000000..5439eb4
--- /dev/null
+++ b/TestFiles/WC/WC056-French.docx
Binary files differ
diff --git a/TestFiles/WC/WC057-Table-Merged-Cell-Mod.docx b/TestFiles/WC/WC057-Table-Merged-Cell-Mod.docx
new file mode 100644
index 0000000..67ee6b0
--- /dev/null
+++ b/TestFiles/WC/WC057-Table-Merged-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC057-Table-Merged-Cell.docx b/TestFiles/WC/WC057-Table-Merged-Cell.docx
new file mode 100644
index 0000000..f0d6853
--- /dev/null
+++ b/TestFiles/WC/WC057-Table-Merged-Cell.docx
Binary files differ
diff --git a/TestFiles/WC/WC058-Table-Merged-Cell-Mod.docx b/TestFiles/WC/WC058-Table-Merged-Cell-Mod.docx
new file mode 100644
index 0000000..edcd1cf
--- /dev/null
+++ b/TestFiles/WC/WC058-Table-Merged-Cell-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC058-Table-Merged-Cell.docx b/TestFiles/WC/WC058-Table-Merged-Cell.docx
new file mode 100644
index 0000000..f0d6853
--- /dev/null
+++ b/TestFiles/WC/WC058-Table-Merged-Cell.docx
Binary files differ
diff --git a/TestFiles/WC/WC059-Footnote-Mod.docx b/TestFiles/WC/WC059-Footnote-Mod.docx
new file mode 100644
index 0000000..2c3af52
--- /dev/null
+++ b/TestFiles/WC/WC059-Footnote-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC059-Footnote.docx b/TestFiles/WC/WC059-Footnote.docx
new file mode 100644
index 0000000..09294f1
--- /dev/null
+++ b/TestFiles/WC/WC059-Footnote.docx
Binary files differ
diff --git a/TestFiles/WC/WC060-Endnote-Mod.docx b/TestFiles/WC/WC060-Endnote-Mod.docx
new file mode 100644
index 0000000..9bffa6b
--- /dev/null
+++ b/TestFiles/WC/WC060-Endnote-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC060-Endnote.docx b/TestFiles/WC/WC060-Endnote.docx
new file mode 100644
index 0000000..09294f1
--- /dev/null
+++ b/TestFiles/WC/WC060-Endnote.docx
Binary files differ
diff --git a/TestFiles/WC/WC061-Style-Added-Mod.docx b/TestFiles/WC/WC061-Style-Added-Mod.docx
new file mode 100644
index 0000000..ac37ade
--- /dev/null
+++ b/TestFiles/WC/WC061-Style-Added-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC061-Style-Added.docx b/TestFiles/WC/WC061-Style-Added.docx
new file mode 100644
index 0000000..ff42395
--- /dev/null
+++ b/TestFiles/WC/WC061-Style-Added.docx
Binary files differ
diff --git a/TestFiles/WC/WC062-New-Char-Style-Added-Mod.docx b/TestFiles/WC/WC062-New-Char-Style-Added-Mod.docx
new file mode 100644
index 0000000..8e59c78
--- /dev/null
+++ b/TestFiles/WC/WC062-New-Char-Style-Added-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC062-New-Char-Style-Added.docx b/TestFiles/WC/WC062-New-Char-Style-Added.docx
new file mode 100644
index 0000000..3b38c2b
--- /dev/null
+++ b/TestFiles/WC/WC062-New-Char-Style-Added.docx
Binary files differ
diff --git a/TestFiles/WC/WC063-Footnote-Mod.docx b/TestFiles/WC/WC063-Footnote-Mod.docx
new file mode 100644
index 0000000..b433a55
--- /dev/null
+++ b/TestFiles/WC/WC063-Footnote-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC063-Footnote.docx b/TestFiles/WC/WC063-Footnote.docx
new file mode 100644
index 0000000..b9b7417
--- /dev/null
+++ b/TestFiles/WC/WC063-Footnote.docx
Binary files differ
diff --git a/TestFiles/WC/WC064-Footnote-Mod.docx b/TestFiles/WC/WC064-Footnote-Mod.docx
new file mode 100644
index 0000000..5982739
--- /dev/null
+++ b/TestFiles/WC/WC064-Footnote-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC064-Footnote.docx b/TestFiles/WC/WC064-Footnote.docx
new file mode 100644
index 0000000..bb1283b
--- /dev/null
+++ b/TestFiles/WC/WC064-Footnote.docx
Binary files differ
diff --git a/TestFiles/WC/WC065-Textbox-Deleted.docx b/TestFiles/WC/WC065-Textbox-Deleted.docx
new file mode 100644
index 0000000..1fae1ed
--- /dev/null
+++ b/TestFiles/WC/WC065-Textbox-Deleted.docx
Binary files differ
diff --git a/TestFiles/WC/WC065-Textbox-Mod.docx b/TestFiles/WC/WC065-Textbox-Mod.docx
new file mode 100644
index 0000000..f12f331
--- /dev/null
+++ b/TestFiles/WC/WC065-Textbox-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC065-Textbox.docx b/TestFiles/WC/WC065-Textbox.docx
new file mode 100644
index 0000000..05523fc
--- /dev/null
+++ b/TestFiles/WC/WC065-Textbox.docx
Binary files differ
diff --git a/TestFiles/WC/WC066-Textbox-Before-Ins-Mod.docx b/TestFiles/WC/WC066-Textbox-Before-Ins-Mod.docx
new file mode 100644
index 0000000..c9a57e2
--- /dev/null
+++ b/TestFiles/WC/WC066-Textbox-Before-Ins-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC066-Textbox-Before-Ins.docx b/TestFiles/WC/WC066-Textbox-Before-Ins.docx
new file mode 100644
index 0000000..4e7c858
--- /dev/null
+++ b/TestFiles/WC/WC066-Textbox-Before-Ins.docx
Binary files differ
diff --git a/TestFiles/WC/WC067-Textbox-Image-Mod.docx b/TestFiles/WC/WC067-Textbox-Image-Mod.docx
new file mode 100644
index 0000000..569b0dc
--- /dev/null
+++ b/TestFiles/WC/WC067-Textbox-Image-Mod.docx
Binary files differ
diff --git a/TestFiles/WC/WC067-Textbox-Image.docx b/TestFiles/WC/WC067-Textbox-Image.docx
new file mode 100644
index 0000000..7714191
--- /dev/null
+++ b/TestFiles/WC/WC067-Textbox-Image.docx
Binary files differ
diff --git a/TestFiles/img.png b/TestFiles/img.png
new file mode 100644
index 0000000..3801852
--- /dev/null
+++ b/TestFiles/img.png
Binary files differ
diff --git a/TestFiles/img2.png b/TestFiles/img2.png
new file mode 100644
index 0000000..94aeda5
--- /dev/null
+++ b/TestFiles/img2.png
Binary files differ
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..03645f0
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,12 @@
+os: Visual Studio 2017
+
+environment:
+ image: Visual Studio 2017
+ Configuration: Release
+
+build_script:
+ - cmd: dotnet restore
+ - cmd: dotnet build
+
+test_script:
+ - cmd: dotnet test .\OpenXmlPowerTools.Tests