Als kleinen Nachtrag zum gestrigen WebCast möchte ich hier kurz auf den Sourcecode eingehen. IT Department sei Dank hatten die Teilnehmer des WebCasts ja die Gelegenheit, „SMS in Action“ zu erleben, als nach einem Update mein Rechner neu gestartet wurde. Ich glaube, es wird wohl nie eine allgemeine Freundschaft zwischen Entwicklern und Administratoren aufkommen. Wir sind da wohl doch zu verschieden. ;-)
Doch zurück zum Wesentlichen, dem Open XML File Format, welches ja bekanntlich Einzug hält in die Geschichte von Office. Die Version 2007 bringt dieses Format als Standard für Excel, Word und Powerpoint. Mit Hilfe von System.XML und System.IO.Packaging (WinFx bzw. .NET Framework 3.0) können Entwickler dieses Format für sich nutzen.
Für viele der Zugriffe benötigen wir Schemainformationen. Diese sind nicht schwer zu finden: Die entsprechende Datei in *.ZIP umbenennen und öffnen. Im Ordner docProps befindet sich core.xml:
Wir ziehen uns also nur die Namespaces heraus, in deren Zuständigkeit die benötigten Dokumenteigenschaften fallen. Die benötigen wir, um unseren Namespacemanager zu füttern, der uns wiederum die Arbeit erleichtert. Wir könnn danach nur noch mit den Kürzeln arbeiten, wenn wir XPath Expressions angeben müssen. Aber dazu weiter unten.
private const string corePropertiesSchema =
"http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
private const string dcPropertiesSchema =
"http://purl.org/dc/elements/1.1/";
Hier en Beispiel zu Lesen von Document Properties beliebiger Dateien der o.g. Programme:
private void GetDocProps(string fName)
{
Package pack = Package.Open(fName, FileMode.Open);
XmlDataDocument xdoc = new XmlDataDocument();
Uri uri = new Uri("/docProps/core.xml", UriKind.Relative);
PackagePart packPart = pack.GetPart(uri);
xdoc.Load(packPart.GetStream(FileMode.Open));
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xdoc.NameTable);
nsmgr.AddNamespace("cp", corePropertiesSchema);
nsmgr.AddNamespace("dc", dcPropertiesSchema);
XmlNode xn;
xn = xdoc.SelectSingleNode("cp:coreProperties/dc:title", nsmgr);
if (xn != null)
tbTitle.Text = xn.InnerText;
pack.Close();
}
Zum Schreiben ist der komplette erste Teil von oben zu übernehmen, nur müssen wir jetzt erst mal schauen, ob der entsprechende Knoten überhaupt in der XML-Datei zu finden ist. Wenn nicht, so müssen wir den erzeugen und an das Dokument anhängen.
...
XmlNode xn;
xn = xdoc.SelectSingleNode("cp:coreProperties/dc:title", nsmgr);
if (xn != null)
xn.InnerText = tbTitle.Text;
else
{
xn = xdoc.CreateElement("dc", "title", dcPropertiesSchema);
xn.InnerText = tbTitle.Text;
xdoc.DocumentElement.AppendChild(xn);
}
using (Stream infoStream = packPart.GetStream(FileMode.Create, FileAccess.Write))
{
xdoc.Save(infoStream);
}
pack.Close();
System.IO.Packaging bietet uns aber zum Lesen der Document Properties eine Abkürzung an: PackageProperties. Damit geht das Lesen bzw. Schreiben flott von der Hand:
private void GetDocProps(string fName)
{
using (Package pack = Package.Open(fName, FileMode.Open))
{
tbTitle.Text = pack.PackageProperties.Title;
tbAuthor.Text = pack.PackageProperties.Creator;
}
}
private void SetDocProps(string fName)
{
using (Package pack = Package.Open(fName, FileMode.Open))
{
pack.PackageProperties.Title = tbTitle.Text;
pack.PackageProperties.Creator = tbAuthor.Text;
}
}
Leider haben wir nicht immer so viel Glück und müssen andere Document Parts mühsam Stück für Stück aus dem Dokument herausholen. Hier als nächstes ein Beispiel, wie aus einem Word-Dokument alle enthaltenen Bilder herausgeholt werden. Dazu müssen wir zuerst einmal den Haupt-DocumentPart heraus ziehen und in dessen Beziehungen alle Image Relationships holen. Die Informationen stehen im relativen Pfad \Word\_rels\document.xml. Im Bild zu sehen ist rId4 eine Bildbeziehung und verweist im Attribut Target auf das Ziel.
Die benötigten Namespaces ziehen wir uns wieder heraus:
private const string documentRelationshipType =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
ptivate const string imageRelationshipType =
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
Nachdem wir wissen, wo die Bilder stehen, brauchen wir sie nur noch heraus zu holen. GetImagesFromDoc schreibt sämtliche Bilder auf eine Liste imageList und gibt sie zurück.
private List<Image> GetImagesFromDoc(string fName)
{
Uri docUri = null;
PackagePart docPart = null;
using (Package xPack = Package.Open(fName, FileMode.Open, FileAccess.Read))
{
foreach (PackageRelationship relationship in xPack.GetRelationshipsByType(documentRelationshipType))
{
docUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);
docPart = xPack.GetPart(docUri);
break;
}
string baseUri = docUri.OriginalString.Remove(docUri.OriginalString.LastIndexOf("/") + 1);
Uri imageUri = null;
PackagePart imagePart = null;
List<Image> imageList = new List<Image>();
foreach (PackageRelationship imageRel in docPart.GetRelationshipsByType(imageRelationshipType))
{
imageUri = new Uri(baseUri + imageRel.TargetUri, UriKind.Relative);
imagePart = xPack.GetPart(imageUri);
imageList.Add(Image.FromStream(imagePart.GetStream(FileMode.Open)));
}
return imageList;
}
}
Das Open XML Format ist zwar offen, aber wir haben gesehen, es ist nicht unbedingt leicht, Teile herauszuholen bzw. wieder hinein u schreiben. Ein gehörige Portion XML-Kenntnisse ist schon vonnöten, gepaart mit dem nötigen Wissen um den Aufbau der Office-Dateien.
Das Beispiel verwendet übrigens eine Referenz auf WindowsBase.DLL aus WinFx (ich habe das Februar CTP), sowie System.Xml, System.IO.Packaging, System.Drawing und System.Collections.Generic.
Weitere Informationen:
Introducing the Microsoft Office (2007) Open XML File Formats
2007 Office System Sample: Open XML File Format Code Snippets for Visual Studio 2005