using System;
using System.IO;
using System.Xml;
using System.Text;
using System.Xml.Xsl;
using System.IO.Packaging;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace BibWordExtender.Data{
/// <summary>
/// BibWord data control and manipulation class.
/// </summary>
public class BibWord
{
#region Constants
/// <summary>
/// Document relationship type.
/// </summary>
private const string DocumentRelationshipType = @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
/// <summary>
/// Custom XML relationship type.
/// </summary>
private const string CustomXmlRelationshipType = @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml";
/// <summary>
/// Bibliography namespace.
/// </summary>
private const string BibliographyNS = @"http://schemas.openxmlformats.org/officeDocument/2006/bibliography";
#endregion Constants
#region Constructors
/// <summary>
/// Initializes a new instance of a BibWord class.
/// </summary>
/// <remarks>
/// Not used as the class consists of static methods only.
/// </remarks>
private BibWord()
{
}
#endregion Constructors
#region Methods
/// <summary>
/// Gets the path to the directory where Word stores its bibliography styles.
/// </summary>
/// <returns>A string containing a path.</returns>
/// <remarks>
/// This function is platform dependent. Currently, only Windows is supported.
/// </remarks>
public static string GetBibliographyStylesPath()
{
if (Environment.OSVersion.Platform == PlatformID.Unix)
{
throw new NotSupportedException("Currently this application only runs " +
"on a Windows OS. If you have information on retrieving the directory" +
" containing the bibliography styles on a different OS, feel free to " +
"contact me.");
}
else
{
Microsoft.Win32.RegistryKey hklm = Microsoft.Win32.Registry.LocalMachine;
hklm = hklm.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Winword.exe");
return ((string)hklm.GetValue("Path") + "Bibliography\\Style");
}
}
/// <summary>
/// Loads information about all the bibliography styles.
/// </summary>
/// <param name="worker">
/// Background worker thread on which this function is run. This parameter
/// is required if progress should be reported.
/// </param>
public static Dictionary<string,string> LoadBibliographyStylesInformation(BackgroundWorker worker)
{
// Place to store the mapping between retrieved style names and style paths.
Dictionary<string, string> result = new Dictionary<string, string>();
// Collection of OfficeStyleKeys. Those are predefined styles of which the
// name can be localized by Microsoft.
Dictionary<string, string> OfficeStyleKeys = new Dictionary<string, string>(10);
OfficeStyleKeys.Add("APA", "APA");
OfficeStyleKeys.Add("CHICAGO", "Chicago");
OfficeStyleKeys.Add("GB7714", "GB7714");
OfficeStyleKeys.Add("GOSTNS", "Gost - Name Sort");
OfficeStyleKeys.Add("GOSTTS", "Gost - Title Sort");
OfficeStyleKeys.Add("ISO690", "ISO 690 - First Element and Date");
OfficeStyleKeys.Add("ISO690NR", "ISO 690 - Numerical Reference");
OfficeStyleKeys.Add("MLA", "MLA");
OfficeStyleKeys.Add("SIST02", "SIST02");
OfficeStyleKeys.Add("TURABIAN", "Turabian");
// Variable for XSL transformations. Although deprecated, this is a lot
// faster than XslCompiledTransform.
XslTransform xslt = new XslTransform();
// Variable for storing the xml fragment that needs transformation.
XmlDocument doc = new XmlDocument();
// Get the path to the directory containing the bibliography styles.
string styleDir = GetBibliographyStylesPath();
// Report progress if required.
if (worker != null)
{
// Report that the style directory was found (0% completed).
worker.ReportProgress(0, "Found style directory at " + styleDir);
}
// Variable containing the number of styles.
int styleMax = Directory.GetFiles(styleDir, "*.xsl").Length;
// Variable containing the number of styles processed so far.
int styleCnt = 0;
// Handle all styles in the directory.
foreach (string stylePath in Directory.GetFiles(styleDir, "*.xsl"))
{
// Used for storing the result of a transformation.
StringBuilder sb = new StringBuilder();
TextWriter tw = new StringWriter(sb);
try
{
// Load the style.
xslt.Load(stylePath);
// Load document for the transformation.
doc.LoadXml("<b:StyleName xmlns:b=\"http://schemas.openxmlformats.org/officeDocument/2006/bibliography\"/>");
// Transform the document.
xslt.Transform(doc, null, tw);
// Get the result of the transformation.
string styleName = sb.ToString();
// If the result is empty, try going for the OfficeStyleKey instead of the StyleName.
if (styleName == "")
{
// Load document for the transformation.
doc.LoadXml("<b:OfficeStyleKey xmlns:b=\"http://schemas.openxmlformats.org/officeDocument/2006/bibliography\"/>");
// Transform the document.
xslt.Transform(doc, null, tw);
// Get the actual name based on the transformation result.
styleName = OfficeStyleKeys[sb.ToString()];
}
// If the result is not empty, add to the map.
if (styleName != "")
{
// Check if the name is not yet in the result map.
if (result.ContainsKey(styleName) == false)
{
result.Add(styleName, stylePath);
}
else
{
// If the style name is already in the map, add the first possible
// indexer after it between brackets.
int cnt = 2;
while (result.ContainsKey(styleName + " (" + cnt + ")") == true)
{
cnt++;
}
styleName += " (" + cnt + ")";
result.Add(styleName, stylePath);
}
}
}
catch (Exception)
{
}
finally
{
// One more style was processed (not necessarily correct).
styleCnt += 1;
// Report progress if required.
if (worker != null)
{
FileInfo fi = new FileInfo(stylePath);
worker.ReportProgress((100 * styleCnt / styleMax), fi.Name);
}
}
}
return result;
}
/// <summary>
/// Gets the name of the bibliography style used in a given Word document.
/// </summary>
/// <param name="docx">Path to the Word document.</param>
/// <param name="styleMap">
/// A mapping between style names (keys) and style paths (values).
/// </param>
/// <returns>A string containing the name of a style.</returns>
public static string GetBibliographyStyle(string docx, Dictionary<string,string> styleMap)
{
// Variable containing the result.
string result = "";
try
{
// Open the docx package.
Package package = Package.Open(docx, FileMode.Open, FileAccess.Read);
// Go over every document.
foreach (PackageRelationship rel in package.GetRelationshipsByType(DocumentRelationshipType))
{
// Create the uri for a document.
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), rel.TargetUri);
// Get the document.
PackagePart document = package.GetPart(documentUri);
// Go over every customXml file related to the document.
foreach (PackageRelationship docRel in document.GetRelationshipsByType(CustomXmlRelationshipType))
{
// Create the uri for a custom xml part of the document.
Uri customXmlUri = PackUriHelper.ResolvePartUri(documentUri, docRel.TargetUri);
// Get the custom xml.
PackagePart customXml = package.GetPart(customXmlUri);
// Load the custom xml into an xml document.
XmlDocument sources = new XmlDocument();
sources.Load(customXml.GetStream());
// Check if this is a custom xml describing a bibliography, i.e., check if it is a sources document.
if (sources.DocumentElement.LocalName.ToLower() == "sources" &&
sources.DocumentElement.NamespaceURI.ToLower() == BibliographyNS.ToLower())
{
XmlNamespaceManager nsm = new XmlNamespaceManager(sources.NameTable);
nsm.AddNamespace("b", BibliographyNS);
// Retrieve the currently selected style.
string selectedStyle = sources.SelectSingleNode("//b:Sources", nsm).Attributes["SelectedStyle"].Value;
// If the selected style is not empty, use it to retrieve the style name.
if (selectedStyle != "")
{
string stylePath = Path.Combine(GetBibliographyStylesPath(), Path.GetFileName(selectedStyle));
// Find the style name through the style path.
foreach (KeyValuePair<string, string> element in styleMap)
{
if (element.Value == stylePath)
{
result = element.Key;
break;
}
}
}
else
{
// If the selected style is empty, try using the StyleName attribute instead.
result = sources.SelectSingleNode("//b:Sources", nsm).Attributes["StyleName"].Value;
}
}
}
}
// Close the docx package.
package.Close();
}
catch (FileFormatException)
{
throw new Exception("Not a valid Word 2007 file.");
}
return result;
}
/// <summary>
/// Extends source elements in an open xml bibliography.
/// </summary>
/// <param name="docx">
/// Path to a Word document containing a bibliography.
/// </param>
/// <param name="stylesheet">
/// Path to the style used to generate the bibliography.
/// </param>
/// <returns>
/// True if an extension took place, false otherwise.
/// </returns>
public static bool ExtendBibliography(string docx, string stylesheet)
{
// Counts the number of bibliographies in the current document which were transformed.
int bibCnt = 0;
// Open the docx package.
Package package = Package.Open(docx, FileMode.Open, FileAccess.ReadWrite);
// Go over every document.
foreach (PackageRelationship rel in package.GetRelationshipsByType(DocumentRelationshipType))
{
// Create the uri for a document.
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), rel.TargetUri);
// Get the document.
PackagePart document = package.GetPart(documentUri);
// Go over every customXml file related to the document.
foreach (PackageRelationship docRel in document.GetRelationshipsByType(CustomXmlRelationshipType))
{
// Create the uri for a custom xml part of the document.
Uri customXmlUri = PackUriHelper.ResolvePartUri(documentUri, docRel.TargetUri);
// Get the custom xml.
PackagePart customXml = package.GetPart(customXmlUri);
// Load the custom xml into an xml document.
XmlDocument sources = new XmlDocument();
sources.Load(customXml.GetStream());
// Check if this is a custom xml describing a bibliography, i.e., check if it is a sources document.
if (sources.DocumentElement.LocalName.ToLower() == "sources" &&
sources.DocumentElement.NamespaceURI.ToLower() == BibliographyNS.ToLower())
{
// Create an XML document to give to the XSL transformation.
XmlDocument BibWordInput = new XmlDocument();
BibWordInput.AppendChild(BibWordInput.CreateXmlDeclaration("1.0", null, null));
// Give the XML document a b:BibOrder element as root.
XmlElement rootElement = BibWordInput.CreateElement("b", "BibWord", BibliographyNS);
BibWordInput.AppendChild(rootElement);
// Attach all the b:Source elements from the sources document to the BibOrderInput document.
XmlNamespaceManager nsm = new XmlNamespaceManager(sources.NameTable);
nsm.AddNamespace("b", BibliographyNS);
foreach (XmlNode node in sources.SelectNodes("//b:Sources/b:Source", nsm))
{
// Add the b:Source element to the XML document to transform.
rootElement.AppendChild(BibWordInput.ImportNode(node, true));
// Remove the b:Source element from the original input.
sources.DocumentElement.RemoveChild(node);
}
try
{
// Do the transformation.
XmlDocument BibWordOutput = new XmlDocument();
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(stylesheet);
using (XmlWriter writer = BibWordOutput.CreateNavigator().AppendChild())
{
xslt.Transform(BibWordInput, (XsltArgumentList)null, writer);
}
// Add the newly obtained b:Source elements back to the orignal document.
foreach (XmlNode node in BibWordOutput.SelectNodes("//b:BibWord/b:Source", nsm))
{
sources.DocumentElement.AppendChild(sources.ImportNode(node, true));
}
// Write the XML document back to the docx.
StreamWriter sw = new StreamWriter(customXml.GetStream(FileMode.Create, FileAccess.ReadWrite));
sw.Write(sources.OuterXml);
sw.Flush();
sw.Close();
// Indicate that one more bibliography was transformed.
bibCnt++;
}
catch (Exception e)
{
// Close the docx package.
package.Close();
// Rethrow the exception rather than returning failure.
throw e;
}
}
}
}
// Close the docx package.
package.Close();
// Give the user a hint that the extension was successful.
return (bibCnt > 0);
}
/// <summary>
/// Cleans all source elements in an open xml bibliography.
/// </summary>
/// <param name="docx">
/// Path to a Word document containing a bibliography.
/// </param>
public static void CleanBibliography(string docx)
{
// Open the docx package.
Package package = Package.Open(docx, FileMode.Open, FileAccess.ReadWrite);
// Go over every document.
foreach (PackageRelationship rel in package.GetRelationshipsByType(DocumentRelationshipType))
{
// Create the uri for a document.
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), rel.TargetUri);
// Get the document.
PackagePart document = package.GetPart(documentUri);
// Go over every customXml file related to the document.
foreach (PackageRelationship docRel in document.GetRelationshipsByType(CustomXmlRelationshipType))
{
// Create the uri for a custom xml part of the document.
Uri customXmlUri = PackUriHelper.ResolvePartUri(documentUri, docRel.TargetUri);
// Get the custom xml.
PackagePart customXml = package.GetPart(customXmlUri);
// Load the custom xml into an xml document.
XmlDocument sources = new XmlDocument();
sources.Load(customXml.GetStream());
// Check if this is a custom xml describing a bibliography, i.e., check if it is a sources document.
if (sources.DocumentElement.LocalName.ToLower() == "sources" &&
sources.DocumentElement.NamespaceURI.ToLower() == BibliographyNS.ToLower())
{
// Convert the entire document to a string.
string bibliography = sources.OuterXml;
// Remove BibOrder element.
Regex regex = new Regex("<[a-zA-Z0-9]*:BibOrder>[a-zA-Z0-9]*</[a-zA-Z0-9]*:BibOrder>");
bibliography = regex.Replace(bibliography, "");
// Remove YearSuffix element.
regex = new Regex("<[a-zA-Z0-9]*:YearSuffix>[a-zA-Z0-9]*</[a-zA-Z0-9]*:YearSuffix>");
bibliography = regex.Replace(bibliography, "");
// Write the XML document back to the docx.
StreamWriter sw = new StreamWriter(customXml.GetStream(FileMode.Create, FileAccess.ReadWrite));
sw.Write(bibliography);
sw.Flush();
sw.Close();
}
}
}
// Close the docx package.
package.Close();
}
#endregion Methods
}
}
|