Open Packaging Conventions (OPC) je standard, který popisuje postupy pro ukládání více datových objektů a jejich vzájemných vztahů v jednom fyzickém kontajneru (souboru). Nejčastější formou datového úložiště je ZIP soubor. Tento archiv obsahuje strukturu souborů a složek a dále pak XML dokumenty, které popisují vztahy mezi těmito soubory a obsahují další metadata. Na specifikaci OPC je založena většina novějších souborových formátů Microsoftu. Například OpenXML, nativní formát souborů v Microsoft Office 2007 (soubory s příponami docx, xlsx, pptx atd.). OPC formát je také základem pro dokumenty ve formátu XPS (XML Papar Specification) nebo úložištěm pro Windows Presentation Foundation (WPF). O pravdivosti mých slov se můžete přesvědčit tak, že DOCX soubor (nejlépe takový, který obsahuje další vložené objekty, jako například obrázky, přejmenujete na ZIP a pak ho otevřete.
Pro práci s “balíčky” z prostředí .NET (verze 3.0 a vyšší) slouží třídy v namespace System.IO.Packaging. Jádrem všeho je abstraktní třída Package, která reprezentuje úložiště, které obsahuje jednotlivé položky, reprezentované třídou PackagePart. Úložištěm pro Package může být principiálně cokoliv – stačí implementovat abstraktní třídu Package a obsloužit podkladové úložiště. Nativní reprezentací balíčku je ale ZIP archiv. Ten je dostupný prostřednictvím třídy ZipPackage.
Rád bych na tomto místě zdůraznil, že popisované třídy nejsou určeny pro obecnou práci se ZIP soubory. Jimi vytvořený ZIP archiv bude kromě vašich dat vždy obsahovat minimálně soubor [Content_Types].xml (a patrně ještě další XML dokumenty ve složkách _rels a package, pokud budete využívat metadata a relace mezi částmi). Pro obecnou práci se ZIP archivy se vám bude spíše hodit specializovaná komponenta. Nepsaným standardem je například volně dostupná knihovna #ZipLib od ICsharpCode. System.IO.Packaging se vám bude hodit v případě, že potřebujete pro vlastní potřebu uchovávat nějaké kompozitní datové struktury. Použitím OPC bez námahy získáte podporu komprese, digitálních podpisů či metadat.
Jedna moje aplikace využívá ke své činnosti několik vzájemně souvisejících XML souborů a XSLT šablon. Pro vytváření záloh těchto dat jsem použil právě OPC formát, který mi umožnil uložit samotné soubory, informace o jejich vzájemném vztahu a metadata – jako například kdo a kdy zálohu vytvořil.
Vytvoření a inicializace OPC balíčku
V první řadě musíte do svého projektu přidat referenci na assembly WindowsBase.dll, čímž se vám zpřístupní namespace System.IO.Packaging.
Třída ZipPackage má metodu Open, pomocí které můžete (v závislosti na předaných argumentech) balíček vytvořit nebo otevřít již existující. Pomocí vlastnosti PackageProperties pak můžete nastavovat metadata společná pro celý balíček – název, typ, autora, datum vytvoření a další. Tato data se interně ukládají do XML souboru /package/services/metadata/core-properties/*.psmdcp.
Následujícím kódem vytvoříte balíček a nastavíte jeho metadata:
// Vytvořit package (pokud existuje, bude přepsána)
Package p = ZipPackage.Open(@"C:\Users\Altair\Downloads\demo.zip", System.IO.FileMode.Create);
// Nastavit vlastnosti
p.PackageProperties.Created = DateTime.Now;
p.PackageProperties.Creator = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
p.PackageProperties.ContentType = "Moje Package";
p.PackageProperties.Identifier = Guid.NewGuid().ToString();
Metadata jsou věc užitečná, nicméně nepovinná. Kompletní seznam a popis dostupných vlastností najdete v OPC specifikaci. Zastavím se pouze u dvou obzvláště užitečných.
ContentType je typ obsahu a může obsahovat informaci o použitém formátu dokumentu. Zejména poud pro název souboru použijete generickou příponu ZIP, může se jednat o vítanou indikaci, že soubor je v nějakém vámi definovaném formátu a že tedy můžete očekávat jistou strukturu. Sem můžete zapsat jakýkoliv text, přes shodu názvu se nejedná o MIME typ, jako je například text/html a podobně.
Identifier je jedinečný identifikátor, který můžete balíčku přiřadit. Může se hodit, pokud chcete obsažená data identifikovat i v případě, že uživatel například soubor přejmenuje.
Přidání obsahu
Přidání části (PackagePart) se děje pomocí metody CreatePart, jejíž argumenty jsou:
- partUri - relativní adresa dané části, v ZIP archivu se jedná o název složky a souboru. Slouží jako hlavní identifikátor části a v rámci jedné package musí být jedinečná.
- contentType – MIME typ obsahu, např. text/plain, text/html, text/xml, image/jpeg… Pokud typ neznáte, použijte application/octet-stream.
- compressionOption – nepovinný, určuje úroveň komprese, stanovuje poměr mezi výpočetní náročností zpracování a efektivitou komprimace. Pokud parametr není uveden, data se pouze uloží, nebudou se komprimovat. To je užitečné, pokud ukládáte data, která mají náhodnou charakteristiku, jako například data šifrovaná nebo již jednou komprimovaná.
Metoda vrací instanci třídy PackagePart. Ta má důležitou metodu GetStream, která vrátí stream, jehož prostřednictvím můžete číst a zapisovat data. Neexistuje přímý způsob, jak do balíčku zahrnout existujcíí soubor, musíte ho zkopírovat pomocí streamu.
Následující kód vytvoří balíček se třemi částmi:
- Textový soubor jménem /readme.txt. Obsah do něj zapíšeme pomocí TextWriteru.
- XML soubor jménem /XmlData/MojeXml.xml. Obsah do něj zapíšeme uložením objektu XmlDocument.
- Obrázek jménem /Wallpapers/Vista.jpg. Vytvoříme ho zkopírováním obsahu existujícího souboru. Vzhledem k tomu, že se jedná již o jednou zkomprimovaná data (formát JPG je komprimovaný), používám možnost data pouze uložit a nesnažit se o opětovnou kompresi.
// Vytvořit package (pokud existuje, bude přepsána)
Package p = ZipPackage.Open(@"C:\Users\Altair\Downloads\demo.zip", System.IO.FileMode.Create);
// Nastavit vlastnosti
p.PackageProperties.Created = DateTime.Now;
p.PackageProperties.Creator = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
p.PackageProperties.ContentType = "Moje Package";
p.PackageProperties.Identifier = Guid.NewGuid().ToString();
// Přidat textový soubor
PackagePart textPart = p.CreatePart(new Uri("/readme.txt", UriKind.Relative), "text/plain", CompressionOption.Maximum);
using (var s = textPart.GetStream())
using (var w = new StreamWriter(s)) {
for (int i = 0; i < 1000; i++) {
w.WriteLine("Nějaký obsah textového souboru.");
}
}
// Přidat XML dokument
XmlDocument doc = new XmlDocument();
doc.AppendChild(doc.CreateElement("root"));
PackagePart xmlPart = p.CreatePart(new Uri("/XmlData/MojeXml.xml", UriKind.Relative), "text/xml", CompressionOption.Maximum);
using (var s = xmlPart.GetStream()) {
doc.Save(s);
}
// Přidat existující soubor
PackagePart jpgPart = p.CreatePart(new Uri("/Wallpapers/Vista.jpg", UriKind.Relative), "image/jpeg");
using (var fileStream = File.OpenRead(@"C:\Windows\Web\Wallpaper\img36.jpg"))
using (var partStream = jpgPart.GetStream()) {
byte[] buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) {
partStream.Write(buffer, 0, bytesRead);
}
}
// Zavřít package
p.Close();
Přečtení obsahu
Pro přečtení obsahu je nutné balíček otevřít a získat jeho jednotlivé části. Buďto můžete použít metodu GetPart, pokud přímo znáte její Uri a nebo metodu GetParts, která vám vrátí kolekci všech částí. Ve druhém případě ale dostanete i systémové části, které jste nevytvořili. Poznáte je podle toho, že jejich content type začíná na application/vnd.openxmlformats-package.
Napsal jsem jednoduchou řádkovou utilitku ShowOPC, která vypíše informace o hlaviččce a všech částech OPC balíčku. Její zdrojový kód je následující:
static void Main(string[] args) {
if (args.Length != 1) {
Console.WriteLine("USAGE: showopc filename");
Environment.Exit(1);
}
Package p = null;
try {
p = Package.Open(args[0], System.IO.FileMode.Open);
// Display properties
Console.WriteLine("Category: {0}", p.PackageProperties.Category);
Console.WriteLine("ContentStatus: {0}", p.PackageProperties.ContentStatus);
Console.WriteLine("ContentType: {0}", p.PackageProperties.ContentType);
Console.WriteLine("Created: {0}", p.PackageProperties.Created);
Console.WriteLine("Creator: {0}", p.PackageProperties.Creator);
Console.WriteLine("Description: {0}", p.PackageProperties.Description);
Console.WriteLine("Identifier: {0}", p.PackageProperties.Identifier);
Console.WriteLine("Keywords: {0}", p.PackageProperties.Keywords);
Console.WriteLine("Language: {0}", p.PackageProperties.Language);
Console.WriteLine("LastModifiedBy: {0}", p.PackageProperties.LastModifiedBy);
Console.WriteLine("LastPrinted: {0}", p.PackageProperties.LastPrinted);
Console.WriteLine("Modified: {0}", p.PackageProperties.Modified);
Console.WriteLine("Revision: {0}", p.PackageProperties.Revision);
Console.WriteLine("Subject: {0}", p.PackageProperties.Subject);
Console.WriteLine("Title: {0}", p.PackageProperties.Title);
Console.WriteLine("Version: {0}", p.PackageProperties.Version);
// Display parts
int i = 0;
foreach (var part in p.GetParts()) {
i++;
Console.WriteLine("Package part {0}:", i);
Console.WriteLine(" URI: {0}", part.Uri);
Console.WriteLine(" Content type: {0}", part.ContentType);
Console.WriteLine(" Size: {0:N0} B", part.GetStream().Length);
Console.WriteLine(" Compression: {0}", part.CompressionOption);
}
}
catch (System.IO.FileNotFoundException) {
Console.WriteLine("File {0} was not found.", fileName);
}
catch (Exception ex) {
Console.WriteLine("Failed to read file {0}:", fileName);
Console.WriteLine(ex.Message);
}
finally {
if (p != null) p.Close();
}
}
Výsledek výše uvedeného kódu pro námi vytvořenou package je následující:
File: C:\Users\Altair\Downloads\demo.zip
Category:
ContentStatus:
ContentType: Moje Package
Created: 2.8.2008 15:13:21
Creator: appaloosa\Altair
Description:
Identifier: e555d798-c6bc-4c41-9f03-49472fed1b9c
Keywords:
Language:
LastModifiedBy:
LastPrinted:
Modified:
Revision:
Subject:
Title:
Version:
Package part 1:
URI: /package/services/metadata/core-properties/d5bce375e53b489b9e8bcdf0b2439a20.psmdcp
Content type: application/vnd.openxmlformats-package.core-properties+xml
Size: 530 B
Compression: NotCompressed
Package part 2:
URI: /readme.txt
Content type: text/plain
Size: 36 000 B
Compression: Maximum
Package part 3:
URI: /Wallpapers/Vista.jpg
Content type: image/jpeg
Size: 717 578 B
Compression: NotCompressed
Package part 4:
URI: /XmlData/MojeXml.xml
Content type: text/xml
Size: 8 B
Compression: Maximum
Package part 5:
URI: /_rels/.rels
Content type: application/vnd.openxmlformats-package.relationships+xml
Size: 365 B
Compression: NotCompressed
Zde uvedeným způsobem můžete manipulovat s OpenXML dokumenty, XPS dokumenty, případně vytvářet vlastní datové formáty. Dodržení OPC standardu vám umožní snadno řešit otázky ukládání metadat, vzájemných vztahů, digitálního podpisu a další.
- Zdrojový kód ShowOPC.exe (3 kB)