| @@ -0,0 +1,33 @@ | |||
| | |||
| Microsoft Visual Studio Solution File, Format Version 12.00 | |||
| # Visual Studio 2012 | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocX.iOS.Test", "DocX.iOS.Test\DocX.iOS.Test.csproj", "{E504B3DA-ED72-4A45-ADCC-F573908A84EB}" | |||
| EndProject | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocX.iOS", "DocX.iOS\DocX.iOS.csproj", "{70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}" | |||
| EndProject | |||
| Global | |||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
| Debug|iPhoneSimulator = Debug|iPhoneSimulator | |||
| Release|iPhone = Release|iPhone | |||
| Release|iPhoneSimulator = Release|iPhoneSimulator | |||
| Debug|iPhone = Debug|iPhone | |||
| EndGlobalSection | |||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Debug|iPhone.ActiveCfg = Debug|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Debug|iPhone.Build.0 = Debug|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Release|iPhone.ActiveCfg = Release|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Release|iPhone.Build.0 = Release|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU | |||
| {70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Debug|iPhone.ActiveCfg = Debug|iPhone | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Debug|iPhone.Build.0 = Debug|iPhone | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Release|iPhone.ActiveCfg = Release|iPhone | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Release|iPhone.Build.0 = Release|iPhone | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator | |||
| {E504B3DA-ED72-4A45-ADCC-F573908A84EB}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator | |||
| EndGlobalSection | |||
| EndGlobal | |||
| @@ -0,0 +1,107 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <PropertyGroup> | |||
| <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||
| <Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform> | |||
| <ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> | |||
| <ProjectGuid>{E504B3DA-ED72-4A45-ADCC-F573908A84EB}</ProjectGuid> | |||
| <OutputType>Exe</OutputType> | |||
| <RootNamespace>DocX.iOS.Test</RootNamespace> | |||
| <IPhoneResourcePrefix>Resources</IPhoneResourcePrefix> | |||
| <AssemblyName>DocX.iOS.Test</AssemblyName> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' "> | |||
| <DebugSymbols>true</DebugSymbols> | |||
| <DebugType>full</DebugType> | |||
| <Optimize>false</Optimize> | |||
| <OutputPath>bin\iPhoneSimulator\Debug</OutputPath> | |||
| <DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants> | |||
| <ErrorReport>prompt</ErrorReport> | |||
| <WarningLevel>4</WarningLevel> | |||
| <ConsolePause>false</ConsolePause> | |||
| <MtouchArch>i386</MtouchArch> | |||
| <MtouchLink>None</MtouchLink> | |||
| <MtouchUseRefCounting>true</MtouchUseRefCounting> | |||
| <MtouchUseSGen>true</MtouchUseSGen> | |||
| <MtouchFastDev>true</MtouchFastDev> | |||
| <MtouchDebug>true</MtouchDebug> | |||
| <CodesignKey>iPhone Developer</CodesignKey> | |||
| <MtouchProfiling>true</MtouchProfiling> | |||
| <MtouchI18n>west</MtouchI18n> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' "> | |||
| <DebugType>full</DebugType> | |||
| <Optimize>true</Optimize> | |||
| <OutputPath>bin\iPhone\Release</OutputPath> | |||
| <ErrorReport>prompt</ErrorReport> | |||
| <WarningLevel>4</WarningLevel> | |||
| <ConsolePause>false</ConsolePause> | |||
| <MtouchArch>ARMv7, ARM64</MtouchArch> | |||
| <CodesignEntitlements>Entitlements.plist</CodesignEntitlements> | |||
| <MtouchFloat32>true</MtouchFloat32> | |||
| <MtouchUseSGen>true</MtouchUseSGen> | |||
| <CodesignKey>iPhone Developer</CodesignKey> | |||
| <MtouchUseRefCounting>true</MtouchUseRefCounting> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' "> | |||
| <DebugType>full</DebugType> | |||
| <Optimize>true</Optimize> | |||
| <OutputPath>bin\iPhoneSimulator\Release</OutputPath> | |||
| <ErrorReport>prompt</ErrorReport> | |||
| <WarningLevel>4</WarningLevel> | |||
| <ConsolePause>false</ConsolePause> | |||
| <MtouchArch>i386</MtouchArch> | |||
| <MtouchLink>None</MtouchLink> | |||
| <MtouchUseRefCounting>true</MtouchUseRefCounting> | |||
| <CodesignKey>iPhone Developer</CodesignKey> | |||
| <MtouchUseSGen>true</MtouchUseSGen> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' "> | |||
| <DebugSymbols>true</DebugSymbols> | |||
| <DebugType>full</DebugType> | |||
| <Optimize>false</Optimize> | |||
| <OutputPath>bin\iPhone\Debug</OutputPath> | |||
| <DefineConstants>DEBUG;ENABLE_TEST_CLOUD;</DefineConstants> | |||
| <ErrorReport>prompt</ErrorReport> | |||
| <WarningLevel>4</WarningLevel> | |||
| <ConsolePause>false</ConsolePause> | |||
| <MtouchArch>ARMv7, ARM64</MtouchArch> | |||
| <CodesignEntitlements>Entitlements.plist</CodesignEntitlements> | |||
| <MtouchFloat32>true</MtouchFloat32> | |||
| <CodesignKey>iPhone Developer</CodesignKey> | |||
| <DeviceSpecificBuild>true</DeviceSpecificBuild> | |||
| <MtouchDebug>true</MtouchDebug> | |||
| <MtouchUseSGen>true</MtouchUseSGen> | |||
| <MtouchUseRefCounting>true</MtouchUseRefCounting> | |||
| <MtouchProfiling>true</MtouchProfiling> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <Reference Include="System" /> | |||
| <Reference Include="System.Xml" /> | |||
| <Reference Include="System.Core" /> | |||
| <Reference Include="Xamarin.iOS" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ImageAsset Include="Resources\Images.xcassets\AppIcons.appiconset\Contents.json" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <InterfaceDefinition Include="Resources\LaunchScreen.xib" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <None Include="Info.plist" /> | |||
| <None Include="Entitlements.plist" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Compile Include="Sources\App.cs" /> | |||
| </ItemGroup> | |||
| <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\DocX.iOS\DocX.iOS.csproj"> | |||
| <Project>{70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}</Project> | |||
| <Name>DocX.iOS</Name> | |||
| </ProjectReference> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Folder Include="Sources\" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,6 @@ | |||
| <?xml version="1.0" encoding="UTF-8" ?> | |||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
| <plist version="1.0"> | |||
| <dict> | |||
| </dict> | |||
| </plist> | |||
| @@ -0,0 +1,43 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
| <plist version="1.0"> | |||
| <dict> | |||
| <key>CFBundleName</key> | |||
| <string>DocX Test</string> | |||
| <key>CFBundleIdentifier</key> | |||
| <string>com.managingsoftware.docx.docx-ios-test</string> | |||
| <key>CFBundleShortVersionString</key> | |||
| <string>1.0</string> | |||
| <key>CFBundleVersion</key> | |||
| <string>1.0</string> | |||
| <key>LSRequiresIPhoneOS</key> | |||
| <true/> | |||
| <key>MinimumOSVersion</key> | |||
| <string>9.3</string> | |||
| <key>UIDeviceFamily</key> | |||
| <array> | |||
| <integer>1</integer> | |||
| <integer>2</integer> | |||
| </array> | |||
| <key>UILaunchStoryboardName</key> | |||
| <string>LaunchScreen</string> | |||
| <key>UIRequiredDeviceCapabilities</key> | |||
| <array> | |||
| <string>armv7</string> | |||
| </array> | |||
| <key>UISupportedInterfaceOrientations</key> | |||
| <array> | |||
| <string>UIInterfaceOrientationPortrait</string> | |||
| <string>UIInterfaceOrientationLandscapeLeft</string> | |||
| <string>UIInterfaceOrientationLandscapeRight</string> | |||
| </array> | |||
| <key>UISupportedInterfaceOrientations~ipad</key> | |||
| <array> | |||
| <string>UIInterfaceOrientationPortrait</string> | |||
| <string>UIInterfaceOrientationLandscapeLeft</string> | |||
| <string>UIInterfaceOrientationLandscapeRight</string> | |||
| </array> | |||
| <key>XSAppIconAssets</key> | |||
| <string>Resources/Images.xcassets/AppIcons.appiconset</string> | |||
| </dict> | |||
| </plist> | |||
| @@ -0,0 +1,162 @@ | |||
| { | |||
| "images": [ | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "29x29", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "29x29", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "29x29", | |||
| "scale": "3x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "40x40", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "40x40", | |||
| "scale": "3x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "57x57", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "57x57", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "60x60", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "iphone", | |||
| "size": "60x60", | |||
| "scale": "3x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "29x29", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "29x29", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "40x40", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "40x40", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "50x50", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "50x50", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "72x72", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "72x72", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "76x76", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "idiom": "ipad", | |||
| "size": "76x76", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "idiom": "car", | |||
| "size": "120x120", | |||
| "scale": "1x" | |||
| }, | |||
| { | |||
| "size": "24x24", | |||
| "idiom": "watch", | |||
| "scale": "2x", | |||
| "role": "notificationCenter", | |||
| "subtype": "38mm" | |||
| }, | |||
| { | |||
| "size": "27.5x27.5", | |||
| "idiom": "watch", | |||
| "scale": "2x", | |||
| "role": "notificationCenter", | |||
| "subtype": "42mm" | |||
| }, | |||
| { | |||
| "size": "29x29", | |||
| "idiom": "watch", | |||
| "role": "companionSettings", | |||
| "scale": "2x" | |||
| }, | |||
| { | |||
| "size": "29x29", | |||
| "idiom": "watch", | |||
| "role": "companionSettings", | |||
| "scale": "3x" | |||
| }, | |||
| { | |||
| "size": "40x40", | |||
| "idiom": "watch", | |||
| "scale": "2x", | |||
| "role": "appLauncher", | |||
| "subtype": "38mm" | |||
| }, | |||
| { | |||
| "size": "44x44", | |||
| "idiom": "watch", | |||
| "scale": "2x", | |||
| "role": "longLook", | |||
| "subtype": "42mm" | |||
| }, | |||
| { | |||
| "size": "86x86", | |||
| "idiom": "watch", | |||
| "scale": "2x", | |||
| "role": "quickLook", | |||
| "subtype": "38mm" | |||
| }, | |||
| { | |||
| "size": "98x98", | |||
| "idiom": "watch", | |||
| "scale": "2x", | |||
| "role": "quickLook", | |||
| "subtype": "42mm" | |||
| } | |||
| ], | |||
| "info": { | |||
| "version": 1, | |||
| "author": "xcode" | |||
| } | |||
| } | |||
| @@ -0,0 +1,32 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
| <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES"> | |||
| <dependencies> | |||
| <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/> | |||
| <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> | |||
| </dependencies> | |||
| <objects> | |||
| <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> | |||
| <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> | |||
| <view contentMode="scaleToFill" id="iN0-l3-epB"> | |||
| <rect key="frame" x="0.0" y="0.0" width="480" height="480"/> | |||
| <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | |||
| <subviews> | |||
| <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="DocX Test" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX" misplaced="YES"> | |||
| <rect key="frame" x="20" y="140" width="441" height="43"/> | |||
| <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/> | |||
| <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> | |||
| <nil key="highlightedColor"/> | |||
| </label> | |||
| </subviews> | |||
| <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> | |||
| <constraints> | |||
| <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/> | |||
| <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/> | |||
| <constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/> | |||
| </constraints> | |||
| <nil key="simulatedStatusBarMetrics"/> | |||
| <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> | |||
| <point key="canvasLocation" x="548" y="455"/> | |||
| </view> | |||
| </objects> | |||
| </document> | |||
| @@ -0,0 +1,143 @@ | |||
| using System; | |||
| using System.IO; | |||
| using Foundation; | |||
| using UIKit; | |||
| namespace DocX.iOS.Test | |||
| { | |||
| [Register ("App")] | |||
| public class App : UIApplicationDelegate | |||
| { | |||
| // Main | |||
| private static void Main (string[] args) | |||
| { | |||
| UIApplication.Main (args, null, "App"); | |||
| } | |||
| // constants | |||
| private const string messageFormat = @" | |||
| <html> | |||
| <head> | |||
| <style type='text/css'> | |||
| html, body | |||
| {{ | |||
| margin: 0; | |||
| padding: 0; | |||
| border: 0; | |||
| font-size: 100%; | |||
| font: inherit; | |||
| font-family: Arial; | |||
| vertical-align: baseline; | |||
| color: #666666; | |||
| }} | |||
| body | |||
| {{ | |||
| padding: 2em 2em; | |||
| }} | |||
| pre | |||
| {{ | |||
| white-space: pre-wrap; | |||
| }} | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <h2>{0}</h2> | |||
| <pre><code>{1}</code></pre> | |||
| </body> | |||
| </html> | |||
| "; | |||
| // publics | |||
| public override UIWindow Window { get; set; } | |||
| // FinishedLaunching | |||
| public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions) | |||
| { | |||
| // create webview + controller | |||
| var controller = new UIViewController (); | |||
| controller.Title = "DocX - Test"; | |||
| var webview = new UIWebView (controller.View.Frame); | |||
| webview.AutoresizingMask = UIViewAutoresizing.FlexibleDimensions; | |||
| webview.ScalesPageToFit = true; | |||
| controller.View.AddSubview (webview); | |||
| // create navigation controller | |||
| var navigation = new UINavigationController (controller); | |||
| // initialize window | |||
| this.Window = new UIWindow (UIScreen.MainScreen.Bounds); | |||
| this.Window.RootViewController = navigation; | |||
| this.Window.MakeKeyAndVisible (); | |||
| // | |||
| try | |||
| { | |||
| // path to our temp docx file | |||
| string pathDocx = Path.Combine(Path.GetTempPath (), "Document.docx"); | |||
| // inform user of what we are about to do | |||
| webview.LoadHtmlString (string.Format(messageFormat, "Generating .docx file, please wait...", pathDocx), null); | |||
| // generating docx | |||
| using (var document = Novacode.DocX.Create (pathDocx)) | |||
| { | |||
| Novacode.Paragraph p = document.InsertParagraph(); | |||
| p.Append("This is a Word Document"); | |||
| p = document.InsertParagraph(); | |||
| p.Append(""); | |||
| p = document.InsertParagraph(); | |||
| p.Append("Hello World"); | |||
| document.Save(); | |||
| } | |||
| // showing docx in webview, with delay, otherwise we don't see our initial message | |||
| this.Invoke(() => { | |||
| webview.LoadRequest (NSUrlRequest.FromUrl (NSUrl.FromFilename (pathDocx))); | |||
| }, 2.0f); | |||
| // done | |||
| } | |||
| catch (Exception e) | |||
| { | |||
| webview.LoadHtmlString (string.Format(messageFormat, "Exception Occurred :", e), null); | |||
| } | |||
| // done | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,200 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
| <PropertyGroup> | |||
| <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> | |||
| <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||
| <ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> | |||
| <ProjectGuid>{70FC6AB5-5C6C-4E7A-92B7-0B24E16FE0EC}</ProjectGuid> | |||
| <OutputType>Library</OutputType> | |||
| <RootNamespace>DocX.iOS</RootNamespace> | |||
| <IPhoneResourcePrefix>Resources</IPhoneResourcePrefix> | |||
| <AssemblyName>DocX.iOS</AssemblyName> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> | |||
| <DebugSymbols>true</DebugSymbols> | |||
| <DebugType>full</DebugType> | |||
| <Optimize>false</Optimize> | |||
| <OutputPath>bin\Debug</OutputPath> | |||
| <DefineConstants>DEBUG;</DefineConstants> | |||
| <ErrorReport>prompt</ErrorReport> | |||
| <WarningLevel>4</WarningLevel> | |||
| <ConsolePause>false</ConsolePause> | |||
| <NoWarn>219;414;168;</NoWarn> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> | |||
| <Optimize>true</Optimize> | |||
| <OutputPath>bin\Release</OutputPath> | |||
| <ErrorReport>prompt</ErrorReport> | |||
| <WarningLevel>4</WarningLevel> | |||
| <ConsolePause>false</ConsolePause> | |||
| <NoWarn>219;414;168;</NoWarn> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <Reference Include="System" /> | |||
| <Reference Include="System.Xml" /> | |||
| <Reference Include="System.Core" /> | |||
| <Reference Include="Xamarin.iOS" /> | |||
| <Reference Include="System.Xml.Linq" /> | |||
| <Reference Include="OpenTK-1.0" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Folder Include="Resources\" /> | |||
| <Folder Include="Sources\" /> | |||
| <Folder Include="Sources\Charts\" /> | |||
| <Folder Include="System\" /> | |||
| <Folder Include="System\IO\" /> | |||
| <Folder Include="System\IO\Packaging\" /> | |||
| <Folder Include="System\Drawing\" /> | |||
| <Folder Include="Zip\" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <Compile Include="Properties\AssemblyInfo.cs" /> | |||
| <Compile Include="..\DocX\Charts\Axis.cs"> | |||
| <Link>Sources\Charts\Axis.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Charts\BarChart.cs"> | |||
| <Link>Sources\Charts\BarChart.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Charts\Chart.cs"> | |||
| <Link>Sources\Charts\Chart.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Charts\LineChart.cs"> | |||
| <Link>Sources\Charts\LineChart.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Charts\PieChart.cs"> | |||
| <Link>Sources\Charts\PieChart.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Charts\XElementHelpers.cs"> | |||
| <Link>Sources\Charts\XElementHelpers.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Bookmark.cs"> | |||
| <Link>Sources\Bookmark.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\BookmarkCollection.cs"> | |||
| <Link>Sources\BookmarkCollection.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Border.cs"> | |||
| <Link>Sources\Border.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Container.cs"> | |||
| <Link>Sources\Container.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\CustomProperty.cs"> | |||
| <Link>Sources\CustomProperty.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\DocProperty.cs"> | |||
| <Link>Sources\DocProperty.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\DocX.cs"> | |||
| <Link>Sources\DocX.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\DocumentTypes.cs"> | |||
| <Link>Sources\DocumentTypes.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\ExtensionsHeadings.cs"> | |||
| <Link>Sources\ExtensionsHeadings.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Footer.cs"> | |||
| <Link>Sources\Footer.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Footers.cs"> | |||
| <Link>Sources\Footers.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\FormattedText.cs"> | |||
| <Link>Sources\FormattedText.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Formatting.cs"> | |||
| <Link>Sources\Formatting.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Header.cs"> | |||
| <Link>Sources\Header.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Headers.cs"> | |||
| <Link>Sources\Headers.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\HelperFunctions.cs"> | |||
| <Link>Sources\HelperFunctions.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Hyperlink.cs"> | |||
| <Link>Sources\Hyperlink.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\IParagraphContainer.cs"> | |||
| <Link>Sources\IParagraphContainer.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Image.cs"> | |||
| <Link>Sources\Image.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\List.cs"> | |||
| <Link>Sources\List.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\PageLayout.cs"> | |||
| <Link>Sources\PageLayout.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Paragraph.cs"> | |||
| <Link>Sources\Paragraph.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Picture.cs"> | |||
| <Link>Sources\Picture.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Section.cs"> | |||
| <Link>Sources\Section.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\Table.cs"> | |||
| <Link>Sources\Table.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\TableOfContents.cs"> | |||
| <Link>Sources\TableOfContents.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\_BaseClasses.cs"> | |||
| <Link>Sources\_BaseClasses.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\_Enumerations.cs"> | |||
| <Link>Sources\_Enumerations.cs</Link> | |||
| </Compile> | |||
| <Compile Include="..\DocX\_Extensions.cs"> | |||
| <Link>Sources\_Extensions.cs</Link> | |||
| </Compile> | |||
| <Compile Include="System\IO\Packaging\Check.cs" /> | |||
| <Compile Include="System\IO\Packaging\CompressionOption.cs" /> | |||
| <Compile Include="System\IO\Packaging\Package.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackagePart.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackagePartCollection.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackageProperties.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackagePropertiesPart.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackageRelationship.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackageRelationshipCollection.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackUriHelper.cs" /> | |||
| <Compile Include="System\IO\Packaging\PackUriParser.cs" /> | |||
| <Compile Include="System\IO\Packaging\TargetMode.cs" /> | |||
| <Compile Include="System\IO\Packaging\ZipPackage.cs" /> | |||
| <Compile Include="System\IO\Packaging\ZipPackagePart.cs" /> | |||
| <Compile Include="System\IO\Packaging\ZipPartStream.cs" /> | |||
| <Compile Include="System\Drawing\ColorTranslator.cs" /> | |||
| <Compile Include="System\Drawing\FontFamily.cs" /> | |||
| <Compile Include="System\Drawing\Image.cs" /> | |||
| <Compile Include="Zip\ZipStorer.cs" /> | |||
| </ItemGroup> | |||
| <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" /> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="..\DocX\Resources\default_styles.xml.gz"> | |||
| <Link>Resources\default_styles.xml.gz</Link> | |||
| <LogicalName>Novacode.Resources.default_styles.xml.gz</LogicalName> | |||
| </EmbeddedResource> | |||
| <EmbeddedResource Include="..\DocX\Resources\numbering.default_bullet_abstract.xml.gz"> | |||
| <Link>Resources\numbering.default_bullet_abstract.xml.gz</Link> | |||
| <LogicalName>Novacode.Resources.default_bullet_abstract.xml.gz</LogicalName> | |||
| </EmbeddedResource> | |||
| <EmbeddedResource Include="..\DocX\Resources\numbering.default_decimal_abstract.xml.gz"> | |||
| <Link>Resources\numbering.default_decimal_abstract.xml.gz</Link> | |||
| <LogicalName>Novacode.Resources.default_decimal_abstract.xml.gz</LogicalName> | |||
| </EmbeddedResource> | |||
| <EmbeddedResource Include="..\DocX\Resources\numbering.xml.gz"> | |||
| <Link>Resources\numbering.xml.gz</Link> | |||
| <LogicalName>Novacode.Resources.numbering.xml.gz</LogicalName> | |||
| </EmbeddedResource> | |||
| <EmbeddedResource Include="..\DocX\Resources\styles.xml.gz"> | |||
| <Link>Resources\styles.xml.gz</Link> | |||
| <LogicalName>Novacode.Resources.styles.xml.gz</LogicalName> | |||
| </EmbeddedResource> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,27 @@ | |||
| using System.Reflection; | |||
| using System.Runtime.CompilerServices; | |||
| // Information about this assembly is defined by the following attributes. | |||
| // Change them to the values specific to your project. | |||
| [assembly: AssemblyTitle ("DocX.iOS")] | |||
| [assembly: AssemblyDescription ("")] | |||
| [assembly: AssemblyConfiguration ("")] | |||
| [assembly: AssemblyCompany ("Managing Software")] | |||
| [assembly: AssemblyProduct ("")] | |||
| [assembly: AssemblyCopyright ("Managing Software")] | |||
| [assembly: AssemblyTrademark ("")] | |||
| [assembly: AssemblyCulture ("")] | |||
| // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". | |||
| // The form "{Major}.{Minor}.*" will automatically update the build and revision, | |||
| // and "{Major}.{Minor}.{Build}.*" will update just the revision. | |||
| [assembly: AssemblyVersion ("1.0.*")] | |||
| // The following attributes are used to specify the signing key for the assembly, | |||
| // if desired. See the Mono documentation for more information about signing. | |||
| //[assembly: AssemblyDelaySign(false)] | |||
| //[assembly: AssemblyKeyFile("")] | |||
| @@ -0,0 +1,89 @@ | |||
| using System; | |||
| using System.Drawing; | |||
| using Foundation; | |||
| using UIKit; | |||
| namespace System.Drawing | |||
| { | |||
| // ColorTranslator | |||
| public static class ColorTranslator | |||
| { | |||
| // FromHtml | |||
| public static Color FromHtml(string color, float alpha = 1.0f) | |||
| { | |||
| color = color.Replace ("#", "").Replace (" ", "").Trim (); | |||
| if (alpha > 1.0f) | |||
| { | |||
| alpha = 1.0f; | |||
| } | |||
| if (alpha < 0.0f) | |||
| { | |||
| alpha = 0.0f; | |||
| } | |||
| int A = 0, R = 0, G = 0, B = 0; | |||
| switch (color.Length) | |||
| { | |||
| case 3 : // #RGB | |||
| { | |||
| A = (int)(alpha * 255); | |||
| R = Convert.ToInt32(string.Format("{0}{0}", color.Substring(0, 1)), 16); | |||
| G = Convert.ToInt32(string.Format("{0}{0}", color.Substring(1, 1)), 16); | |||
| B = Convert.ToInt32(string.Format("{0}{0}", color.Substring(2, 1)), 16); | |||
| break; | |||
| } | |||
| case 4 : // #ARGB | |||
| { | |||
| A = Convert.ToInt32(string.Format("{0}{0}", color.Substring(0, 1)), 16); | |||
| R = Convert.ToInt32(string.Format("{0}{0}", color.Substring(1, 1)), 16); | |||
| G = Convert.ToInt32(string.Format("{0}{0}", color.Substring(2, 1)), 16); | |||
| B = Convert.ToInt32(string.Format("{0}{0}", color.Substring(3, 1)), 16); | |||
| break; | |||
| } | |||
| case 6 : // #RRGGBB | |||
| { | |||
| A = (int)(alpha * 255); | |||
| R = Convert.ToInt32(color.Substring(0, 2), 16); | |||
| G = Convert.ToInt32(color.Substring(2, 2), 16); | |||
| B = Convert.ToInt32(color.Substring(4, 2), 16); | |||
| break; | |||
| } | |||
| case 8 : // #RRGGBB | |||
| { | |||
| A = Convert.ToInt32(color.Substring(0, 2), 16); | |||
| R = Convert.ToInt32(color.Substring(2, 2), 16); | |||
| G = Convert.ToInt32(color.Substring(4, 2), 16); | |||
| B = Convert.ToInt32(color.Substring(6, 2), 16); | |||
| break; | |||
| } | |||
| } | |||
| return Color.FromArgb (A, R, G, B); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| using System; | |||
| namespace System.Drawing | |||
| { | |||
| // FontFamily | |||
| public class FontFamily : MarshalByRefObject | |||
| { | |||
| // properties | |||
| public string Name { get; private set; } | |||
| // constructor | |||
| public FontFamily (string name) | |||
| { | |||
| this.Name = name; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,85 @@ | |||
| using System; | |||
| using System.IO; | |||
| using Foundation; | |||
| using UIKit; | |||
| namespace System.Drawing | |||
| { | |||
| // Image | |||
| public class Image : MarshalByRefObject, IDisposable | |||
| { | |||
| // FromStream | |||
| public static Image FromStream(Stream stream) | |||
| { | |||
| return new Image (stream); | |||
| } | |||
| // properties | |||
| public bool Disposed { get; private set; } | |||
| public int Height { get; private set; } | |||
| public int Width { get; private set; } | |||
| // constructor | |||
| private Image (Stream stream) | |||
| { | |||
| using (var image = UIImage.LoadFromData (NSData.FromStream (stream))) | |||
| { | |||
| this.Width = (int)image.Size.Width; | |||
| this.Height = (int)image.Size.Height; | |||
| } | |||
| } | |||
| // destructor | |||
| ~Image() | |||
| { | |||
| Dispose(false); | |||
| } | |||
| // Dispose | |||
| public void Dispose() | |||
| { | |||
| this.Dispose(true); | |||
| GC.SuppressFinalize(this); | |||
| } | |||
| // Dispose | |||
| protected virtual void Dispose(bool disposing) | |||
| { | |||
| if (this.Disposed) | |||
| { | |||
| return; | |||
| } | |||
| try | |||
| { | |||
| try | |||
| { | |||
| if (disposing) | |||
| { | |||
| // dispose managed | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| // dispose unmanaged | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| this.Disposed = true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,163 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| //using DocumentFormat.OpenXml.Packaging; | |||
| namespace System.IO.Packaging | |||
| { | |||
| internal static class Check | |||
| { | |||
| static void NotNull(object o, string name) | |||
| { | |||
| if (o == null) | |||
| throw new ArgumentNullException(name); | |||
| } | |||
| public static void ContentTypeIsValid(string contentType) | |||
| { | |||
| if (string.IsNullOrEmpty(contentType)) | |||
| return; | |||
| // Must be in form of: type/subtype | |||
| int index = contentType.IndexOf('/'); | |||
| bool result = (index > 0) && contentType.Length > (index + 1) && contentType.IndexOf('/', index + 1) == -1; | |||
| if (!result) | |||
| throw new ArgumentException("contentType", "contentType must be in the form of 'type/subtype'"); | |||
| } | |||
| public static void Id(object id) | |||
| { | |||
| NotNull(id, "id"); | |||
| } | |||
| public static void IdIsValid(string id) | |||
| { | |||
| if (id == null) | |||
| return; | |||
| // If the ID is a zero string, need to throw a ArgNullEx | |||
| if (id.Length == 0) | |||
| throw new ArgumentNullException("id", "Cannot be whitespace or empty"); | |||
| // FIXME: I need to XSD parse this to make sure it's valid | |||
| // If it's not, throw an XmlException | |||
| } | |||
| private static bool EmptyOrBlank(string s) | |||
| { | |||
| return (s != null && (s == "" || s.Trim().Length == 0)); | |||
| } | |||
| private static void PartUriDoesntEndWithSlash(Uri uri) | |||
| { | |||
| var s = !uri.IsAbsoluteUri ? uri.OriginalString | |||
| : uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped); | |||
| // We allow '/' at uri's beggining. | |||
| if ((s.Length > 1) && s.EndsWith("/")) | |||
| { | |||
| throw new ArgumentException("Part URI cannot end with a forward slash."); | |||
| } | |||
| } | |||
| public static void Package(object package) | |||
| { | |||
| if (package == null) | |||
| throw new ArgumentNullException("package"); | |||
| } | |||
| public static void PackageUri(object packageUri) | |||
| { | |||
| NotNull(packageUri, "packageUri"); | |||
| } | |||
| public static void PackageUriIsValid(Uri packageUri) | |||
| { | |||
| if (!packageUri.IsAbsoluteUri) | |||
| throw new ArgumentException("packageUri", "Uri must be absolute"); | |||
| } | |||
| public static void PackUriIsValid(Uri packUri) | |||
| { | |||
| if (!packUri.IsAbsoluteUri) | |||
| throw new ArgumentException("packUri", "PackUris must be absolute"); | |||
| if (packUri.Scheme != PackUriHelper.UriSchemePack) | |||
| throw new ArgumentException("packUri", "Uri scheme is not a valid PackUri scheme"); | |||
| } | |||
| public static void PartUri(object partUri) | |||
| { | |||
| if (partUri == null) | |||
| throw new ArgumentNullException("partUri"); | |||
| } | |||
| public static void PartUriIsValid(Uri partUri) | |||
| { | |||
| if (!partUri.OriginalString.StartsWith("/")) | |||
| throw new ArgumentException("PartUris must start with '/'"); | |||
| if (partUri.IsAbsoluteUri) | |||
| throw new ArgumentException("PartUris cannot be absolute"); | |||
| } | |||
| public static void RelationshipTypeIsValid(string relationshipType) | |||
| { | |||
| if (relationshipType == null) | |||
| throw new ArgumentNullException("relationshipType"); | |||
| if (EmptyOrBlank(relationshipType)) | |||
| throw new ArgumentException("relationshipType", "Cannot be whitespace or empty"); | |||
| } | |||
| public static void PartUri(Uri partUri) | |||
| { | |||
| if (partUri == null) | |||
| throw new ArgumentNullException("partUri"); | |||
| if (partUri.IsAbsoluteUri) | |||
| throw new ArgumentException("partUri", "Absolute URIs are not supported"); | |||
| if (string.IsNullOrEmpty(partUri.OriginalString)) | |||
| throw new ArgumentException("partUri", "Part uri cannot be an empty string"); | |||
| } | |||
| public static void PackUri(Uri packUri) | |||
| { | |||
| NotNull(packUri, "packUri"); | |||
| } | |||
| public static void SourcePartUri(Uri sourcePartUri) | |||
| { | |||
| NotNull(sourcePartUri, "sourcePartUri"); | |||
| PartUriDoesntEndWithSlash(sourcePartUri); | |||
| } | |||
| public static void TargetPartUri(Uri targetPartUri) | |||
| { | |||
| NotNull(targetPartUri, "targetPartUri"); | |||
| PartUriDoesntEndWithSlash(targetPartUri); | |||
| } | |||
| public static void SourceUri(Uri sourceUri) | |||
| { | |||
| if (sourceUri == null) | |||
| throw new ArgumentNullException("sourceUri"); | |||
| // if (sourceUri.IsAbsoluteUri) | |||
| // throw new ArgumentException ("sourceUri", "Absolute URIs are not supported"); | |||
| if (string.IsNullOrEmpty(sourceUri.OriginalString)) | |||
| throw new ArgumentException("sourceUri", "Part uri cannot be an empty string"); | |||
| } | |||
| public static void TargetUri(Uri targetUri) | |||
| { | |||
| if (targetUri == null) | |||
| throw new ArgumentNullException("targetUri"); | |||
| // if (targetUri.IsAbsoluteUri) | |||
| // throw new ArgumentException ("targetUri", "Absolute URIs are not supported"); | |||
| if (string.IsNullOrEmpty(targetUri.OriginalString)) | |||
| throw new ArgumentException("targetUri", "Part uri cannot be an empty string"); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public enum CompressionOption | |||
| { | |||
| NotCompressed = -1, | |||
| Normal, | |||
| Maximum, | |||
| Fast, | |||
| SuperFast, | |||
| } | |||
| } | |||
| @@ -0,0 +1,185 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public static class PackUriHelper | |||
| { | |||
| public static readonly string UriSchemePack = "pack"; | |||
| static readonly Uri PackSchemeUri = new Uri("pack://", UriKind.Absolute); | |||
| static readonly char[] _escapedChars = new char[] { '%', ',', '?', '@' }; | |||
| static PackUriHelper() | |||
| { | |||
| if (!UriParser.IsKnownScheme(UriSchemePack)) | |||
| UriParser.Register(new PackUriParser(), UriSchemePack, -1); | |||
| } | |||
| public static int ComparePackUri(Uri firstPackUri, Uri secondPackUri) | |||
| { | |||
| if (firstPackUri == null) | |||
| return secondPackUri == null ? 0 : -1; | |||
| if (secondPackUri == null) | |||
| return 1; | |||
| Check.PackUriIsValid(firstPackUri); | |||
| Check.PackUriIsValid(secondPackUri); | |||
| // FIXME: What exactly is compared. Lets assume originalstring | |||
| return firstPackUri.OriginalString.CompareTo(secondPackUri.OriginalString); | |||
| } | |||
| public static int ComparePartUri(Uri firstPartUri, Uri secondPartUri) | |||
| { | |||
| if (firstPartUri == null) | |||
| return secondPartUri == null ? 0 : -1; | |||
| if (secondPartUri == null) | |||
| return 1; | |||
| Check.PartUriIsValid(firstPartUri); | |||
| Check.PartUriIsValid(secondPartUri); | |||
| return firstPartUri.OriginalString.CompareTo(secondPartUri.OriginalString); | |||
| } | |||
| public static Uri Create(Uri packageUri) | |||
| { | |||
| return Create(packageUri, null, null); | |||
| } | |||
| public static Uri Create(Uri packageUri, Uri partUri) | |||
| { | |||
| return Create(packageUri, partUri, null); | |||
| } | |||
| public static Uri Create(Uri packageUri, Uri partUri, string fragment) | |||
| { | |||
| Check.PackageUri(packageUri); | |||
| Check.PackageUriIsValid(packageUri); | |||
| if (partUri != null) | |||
| Check.PartUriIsValid(partUri); | |||
| if (fragment != null && (fragment.Length == 0 || fragment[0] != '#')) | |||
| throw new ArgumentException("Fragment", "Fragment must not be empty and must start with '#'"); | |||
| // FIXME: Validate that partUri is a valid one? Must be relative, must start with '/' | |||
| // First replace the slashes, then escape the special characters | |||
| //string orig = packageUri.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped); | |||
| string orig = packageUri.OriginalString; | |||
| foreach (var ch in _escapedChars) | |||
| { | |||
| orig = !orig.Contains(ch.ToString()) ? orig : orig.Replace(ch.ToString(), Uri.HexEscape(ch)); | |||
| } | |||
| orig = orig.Replace('/', ','); | |||
| if (partUri != null) | |||
| orig += partUri.OriginalString; | |||
| if ((fragment == null && partUri == null) && orig[orig.Length - 1] != '/') | |||
| orig += '/'; | |||
| if (fragment != null) | |||
| orig += fragment; | |||
| return new Uri("pack://" + orig); | |||
| } | |||
| public static Uri CreatePartUri(Uri partUri) | |||
| { | |||
| Check.PartUri(partUri); | |||
| if (partUri.OriginalString[0] != '/') | |||
| partUri = new Uri("/" + partUri.ToString(), UriKind.Relative); | |||
| return partUri; | |||
| } | |||
| public static Uri GetNormalizedPartUri(Uri partUri) | |||
| { | |||
| Check.PartUri(partUri); | |||
| return new Uri(partUri.ToString().ToUpperInvariant(), UriKind.Relative); | |||
| } | |||
| public static Uri GetPackageUri(Uri packUri) | |||
| { | |||
| Check.PackUri(packUri); | |||
| Check.PackUriIsValid(packUri); | |||
| string s = packUri.Host.Replace(',', '/'); | |||
| return new Uri(Uri.UnescapeDataString(s), UriKind.RelativeOrAbsolute); | |||
| } | |||
| public static Uri GetPartUri(Uri packUri) | |||
| { | |||
| Check.PackUri(packUri); | |||
| Check.PackUriIsValid(packUri); | |||
| if (string.IsNullOrEmpty(packUri.AbsolutePath) || packUri.AbsolutePath == "/") | |||
| return null; | |||
| return new Uri(packUri.AbsolutePath, UriKind.Relative); | |||
| } | |||
| public static Uri GetRelationshipPartUri(Uri partUri) | |||
| { | |||
| Check.PartUri(partUri); | |||
| Check.PartUriIsValid(partUri); | |||
| int index = partUri.OriginalString.LastIndexOf("/"); | |||
| string s = partUri.OriginalString.Substring(0, index); | |||
| s += "/_rels" + partUri.OriginalString.Substring(index) + ".rels"; | |||
| return new Uri(s, UriKind.Relative); | |||
| } | |||
| public static Uri GetRelativeUri(Uri sourcePartUri, Uri targetPartUri) | |||
| { | |||
| Check.SourcePartUri(sourcePartUri); | |||
| Check.TargetPartUri(targetPartUri); | |||
| Uri uri = new Uri("http://fake.com"); | |||
| Uri a = new Uri(uri, sourcePartUri.OriginalString); | |||
| Uri b = new Uri(uri, targetPartUri.OriginalString); | |||
| return a.MakeRelativeUri(b); | |||
| } | |||
| public static Uri GetSourcePartUriFromRelationshipPartUri(Uri relationshipPartUri) | |||
| { | |||
| //Check.RelationshipPartUri (relationshipPartUri); | |||
| if (!IsRelationshipPartUri(relationshipPartUri)) | |||
| throw new Exception("is not a relationship part!?"); | |||
| return null; | |||
| } | |||
| public static bool IsRelationshipPartUri(Uri partUri) | |||
| { | |||
| Check.PartUri(partUri); | |||
| return partUri.OriginalString.StartsWith("/_rels") && partUri.OriginalString.EndsWith(".rels"); | |||
| } | |||
| public static Uri ResolvePartUri(Uri sourcePartUri, Uri targetUri) | |||
| { | |||
| Check.SourcePartUri(sourcePartUri); | |||
| Check.TargetUri(targetUri); | |||
| Check.PartUriIsValid(sourcePartUri); | |||
| // commented out because on Android they are absolute file:/// | |||
| // if (targetUri.IsAbsoluteUri) | |||
| // throw new ArgumentException("targetUri", "Absolute URIs are not supported"); | |||
| Uri uri = new Uri("http://fake.com"); | |||
| uri = new Uri(uri, sourcePartUri); | |||
| uri = new Uri(uri, targetUri); | |||
| // Trim out 'http://fake.com' | |||
| return new Uri(uri.OriginalString.Substring(15), UriKind.Relative); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,109 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| class PackUriParser : GenericUriParser | |||
| { | |||
| const string SchemaName = "pack"; | |||
| StringBuilder builder = new StringBuilder(); | |||
| public PackUriParser() | |||
| : base(GenericUriParserOptions.Default) | |||
| { | |||
| } | |||
| protected override string GetComponents(Uri uri, UriComponents components, UriFormat format) | |||
| { | |||
| string s = uri.OriginalString; | |||
| builder.Remove(0, builder.Length); | |||
| if ((components & UriComponents.Scheme) == UriComponents.Scheme) | |||
| { | |||
| int start = 0; | |||
| int end = s.IndexOf(':'); | |||
| builder.Append(s, start, end - start); | |||
| } | |||
| if ((components & UriComponents.Host) == UriComponents.Host) | |||
| { | |||
| // Skip past pack:// | |||
| int start = 7; | |||
| int end = s.IndexOf('/', start); | |||
| if (end == -1) | |||
| end = s.Length; | |||
| if (builder.Length > 0) | |||
| builder.Append("://"); | |||
| builder.Append(s, start, end - start); | |||
| } | |||
| // Port is always -1, so i think i can ignore both Port and StrongPort | |||
| // Normally they'd get parsed here | |||
| if ((components & UriComponents.Path) == UriComponents.Path) | |||
| { | |||
| // Skip past pack:// | |||
| int start = s.IndexOf('/', 7); | |||
| int end = s.IndexOf('?'); | |||
| if (end == -1) | |||
| end = s.IndexOf('#'); | |||
| if (end == -1) | |||
| end = s.Length; | |||
| if ((components & UriComponents.KeepDelimiter) != UriComponents.KeepDelimiter && | |||
| builder.Length == 0) | |||
| start++; | |||
| if (start > 0) builder.Append(s, start, end - start); | |||
| } | |||
| if ((components & UriComponents.Query) == UriComponents.Query) | |||
| { | |||
| int index = s.IndexOf('?'); | |||
| if (index != -1) | |||
| { | |||
| if ((components & UriComponents.KeepDelimiter) != UriComponents.KeepDelimiter && | |||
| builder.Length == 0) | |||
| index++; | |||
| int fragIndex = s.IndexOf('#'); | |||
| int end = fragIndex == -1 ? s.Length : fragIndex; | |||
| builder.Append(s, index, end - index); | |||
| } | |||
| } | |||
| if ((components & UriComponents.Fragment) == UriComponents.Fragment) | |||
| { | |||
| int index = s.IndexOf('#'); | |||
| if (index != -1) | |||
| { | |||
| if ((components & UriComponents.KeepDelimiter) != UriComponents.KeepDelimiter && | |||
| builder.Length == 0) | |||
| index++; | |||
| builder.Append(s, index, s.Length - index); | |||
| } | |||
| } | |||
| return builder.ToString(); | |||
| } | |||
| protected override void InitializeAndValidate(Uri uri, out UriFormatException parsingError) | |||
| { | |||
| parsingError = null; | |||
| } | |||
| protected override UriParser OnNewUri() | |||
| { | |||
| return new PackUriParser(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,488 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using System.Xml; | |||
| //using DocumentFormat.OpenXml.Packaging; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public abstract class Package : IDisposable | |||
| { | |||
| internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml"; | |||
| internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships"; | |||
| internal static readonly Uri RelationshipUri = new Uri("/_rels/.rels", UriKind.Relative); | |||
| private PackageProperties packageProperties; | |||
| private PackagePartCollection partsCollection; | |||
| private Dictionary<string, PackageRelationship> relationships; | |||
| private PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection(); | |||
| private Uri Uri = new Uri("/", UriKind.Relative); | |||
| private bool Disposed { get; set; } | |||
| public FileAccess FileOpenAccess { get; private set; } | |||
| public PackageProperties PackageProperties | |||
| { | |||
| get | |||
| { | |||
| // PackageProperties are loaded when the relationships are loaded. | |||
| // Therefore ensure we've already loaded the relationships. | |||
| int count = Relationships.Count; | |||
| if (packageProperties == null) | |||
| { | |||
| packageProperties = new PackagePropertiesPart(); | |||
| packageProperties.Package = this; | |||
| } | |||
| return packageProperties; | |||
| } | |||
| } | |||
| private PackagePartCollection PartsCollection | |||
| { | |||
| get | |||
| { | |||
| if (partsCollection == null) | |||
| { | |||
| partsCollection = new PackagePartCollection(); | |||
| partsCollection.Parts.AddRange(GetPartsCore()); | |||
| } | |||
| return partsCollection; | |||
| } | |||
| } | |||
| private int RelationshipId { get; set; } | |||
| private Dictionary<string, PackageRelationship> Relationships | |||
| { | |||
| get | |||
| { | |||
| if (relationships == null) | |||
| { | |||
| LoadRelationships(); | |||
| } | |||
| return relationships; | |||
| } | |||
| } | |||
| private bool Streaming { get; set; } | |||
| protected Package(FileAccess openFileAccess) | |||
| : this(openFileAccess, false) | |||
| { | |||
| } | |||
| protected Package(FileAccess openFileAccess, bool streaming) | |||
| { | |||
| FileOpenAccess = openFileAccess; | |||
| Streaming = streaming; | |||
| } | |||
| internal void CheckIsReadOnly() | |||
| { | |||
| if (FileOpenAccess == FileAccess.Read) | |||
| throw new IOException("Operation not valid when package is read-only"); | |||
| } | |||
| public void Close() | |||
| { | |||
| // FIXME: Ensure that Flush is actually called before dispose | |||
| ((IDisposable) this).Dispose(); | |||
| } | |||
| public PackagePart CreatePart(Uri partUri, string contentType) | |||
| { | |||
| return CreatePart(partUri, contentType, CompressionOption.NotCompressed); | |||
| } | |||
| public PackagePart CreatePart(Uri partUri, string contentType, CompressionOption compressionOption) | |||
| { | |||
| CheckIsReadOnly(); | |||
| Check.PartUri(partUri); | |||
| Check.ContentTypeIsValid(contentType); | |||
| if (PartExists(partUri)) | |||
| throw new InvalidOperationException("This partUri is already contained in the package"); | |||
| PackagePart part = CreatePartCore(partUri, contentType, compressionOption); | |||
| PartsCollection.Parts.Add(part); | |||
| return part; | |||
| } | |||
| protected abstract PackagePart CreatePartCore(Uri partUri, string contentType, | |||
| CompressionOption compressionOption); | |||
| public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType) | |||
| { | |||
| return CreateRelationship(targetUri, targetMode, relationshipType, null); | |||
| } | |||
| public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, | |||
| string id) | |||
| { | |||
| return CreateRelationship(targetUri, targetMode, relationshipType, id, false); | |||
| } | |||
| internal PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, | |||
| string id, bool loading) | |||
| { | |||
| if (!loading) | |||
| CheckIsReadOnly(); | |||
| Check.TargetUri(targetUri); | |||
| if (targetUri.IsAbsoluteUri && targetMode == TargetMode.Internal) | |||
| throw new ArgumentException("TargetUri cannot be absolute for an internal relationship"); | |||
| Check.RelationshipTypeIsValid(relationshipType); | |||
| Check.IdIsValid(id); | |||
| if (id == null) | |||
| id = NextId(); | |||
| PackageRelationship r = new PackageRelationship(id, this, relationshipType, Uri, targetMode, targetUri); | |||
| if (!PartExists(RelationshipUri)) | |||
| CreatePartCore(RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed).IsRelationship | |||
| = true; | |||
| Relationships.Add(r.Id, r); | |||
| relationshipsCollection.Relationships.Add(r); | |||
| if (!loading) | |||
| { | |||
| using (Stream s = GetPart(RelationshipUri).GetStream()) | |||
| WriteRelationships(relationships, s); | |||
| } | |||
| return r; | |||
| } | |||
| public void DeletePart(Uri partUri) | |||
| { | |||
| CheckIsReadOnly(); | |||
| Check.PartUri(partUri); | |||
| PackagePart part = GetPart(partUri); | |||
| if (part != null) | |||
| { | |||
| if (part.Package == null) | |||
| throw new InvalidOperationException("This part has already been removed"); | |||
| // FIXME: MS.NET doesn't remove the relationship part | |||
| // Instead it throws an exception if you try to use it | |||
| if (PartExists(part.RelationshipsPartUri)) | |||
| GetPart(part.RelationshipsPartUri).Package = null; | |||
| part.Package = null; | |||
| DeletePartCore(partUri); | |||
| PartsCollection.Parts.RemoveAll(p => p.Uri == partUri); | |||
| } | |||
| } | |||
| protected abstract void DeletePartCore(Uri partUri); | |||
| public void DeleteRelationship(string id) | |||
| { | |||
| Check.Id(id); | |||
| CheckIsReadOnly(); | |||
| Relationships.Remove(id); | |||
| relationshipsCollection.Relationships.RemoveAll(r => r.Id == id); | |||
| if (Relationships.Count > 0) | |||
| using (Stream s = GetPart(RelationshipUri).GetStream()) | |||
| WriteRelationships(relationships, s); | |||
| else | |||
| DeletePart(RelationshipUri); | |||
| } | |||
| void IDisposable.Dispose() | |||
| { | |||
| if (!Disposed) | |||
| { | |||
| Flush(); | |||
| Dispose(true); | |||
| Disposed = true; | |||
| } | |||
| } | |||
| protected virtual void Dispose(bool disposing) | |||
| { | |||
| // Nothing here needs to be disposed of | |||
| } | |||
| private bool flushing = false; | |||
| public void Flush() | |||
| { | |||
| if (FileOpenAccess == FileAccess.Read || flushing) | |||
| return; | |||
| flushing = true; | |||
| // Ensure we've loaded the relationships, parts and properties | |||
| int count = Relationships.Count; | |||
| if (packageProperties != null) | |||
| packageProperties.Flush(); | |||
| FlushCore(); | |||
| flushing = false; | |||
| } | |||
| protected abstract void FlushCore(); | |||
| public PackagePart GetPart(Uri partUri) | |||
| { | |||
| Check.PartUri(partUri); | |||
| return GetPartCore(partUri); | |||
| } | |||
| protected abstract PackagePart GetPartCore(Uri partUri); | |||
| public PackagePartCollection GetParts() | |||
| { | |||
| PartsCollection.Parts.Clear(); | |||
| PartsCollection.Parts.AddRange(GetPartsCore()); | |||
| return PartsCollection; | |||
| } | |||
| protected abstract PackagePart[] GetPartsCore(); | |||
| public PackageRelationship GetRelationship(string id) | |||
| { | |||
| return Relationships[id]; | |||
| } | |||
| public PackageRelationshipCollection GetRelationships() | |||
| { | |||
| // Ensure the Relationships dict is instantiated first. | |||
| ICollection<PackageRelationship> rels = Relationships.Values; | |||
| relationshipsCollection.Relationships.Clear(); | |||
| relationshipsCollection.Relationships.AddRange(rels); | |||
| return relationshipsCollection; | |||
| } | |||
| public PackageRelationshipCollection GetRelationshipsByType(string relationshipType) | |||
| { | |||
| PackageRelationshipCollection collection = new PackageRelationshipCollection(); | |||
| foreach (PackageRelationship r in Relationships.Values) | |||
| if (r.RelationshipType == relationshipType) | |||
| collection.Relationships.Add(r); | |||
| return collection; | |||
| } | |||
| private void LoadRelationships() | |||
| { | |||
| relationships = new Dictionary<string, PackageRelationship>(); | |||
| if (!PartExists(RelationshipUri)) | |||
| return; | |||
| using (Stream stream = GetPart(RelationshipUri).GetStream()) | |||
| { | |||
| XmlDocument doc = new XmlDocument(); | |||
| doc.Load(stream); | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| manager.AddNamespace("rel", RelationshipNamespace); | |||
| foreach (XmlNode node in doc.SelectNodes("/rel:Relationships/*", manager)) | |||
| { | |||
| TargetMode mode = TargetMode.Internal; | |||
| if (node.Attributes["TargetMode"] != null) | |||
| mode = (TargetMode) Enum.Parse(typeof (TargetMode), node.Attributes["TargetMode"].Value); | |||
| Uri uri; | |||
| try | |||
| { | |||
| uri = new Uri(node.Attributes["Target"].Value.ToString(), UriKind.Relative); | |||
| } | |||
| catch | |||
| { | |||
| uri = new Uri(node.Attributes["Target"].Value.ToString(), UriKind.Absolute); | |||
| } | |||
| CreateRelationship(uri, | |||
| mode, | |||
| node.Attributes["Type"].Value.ToString(), | |||
| node.Attributes["Id"].Value.ToString(), | |||
| true); | |||
| } | |||
| foreach (PackageRelationship r in relationships.Values) | |||
| { | |||
| if (r.RelationshipType == PackageProperties.NSPackagePropertiesRelation) | |||
| { | |||
| PackagePart part = GetPart(PackUriHelper.ResolvePartUri(Uri, r.TargetUri)); | |||
| packageProperties = new PackagePropertiesPart(); | |||
| packageProperties.Package = this; | |||
| packageProperties.Part = part; | |||
| packageProperties.LoadFrom(part.GetStream()); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private string NextId() | |||
| { | |||
| while (true) | |||
| { | |||
| string s = "Re" + RelationshipId.ToString(); | |||
| if (!Relationships.ContainsKey(s)) | |||
| return s; | |||
| RelationshipId++; | |||
| } | |||
| } | |||
| public static Package Open(Stream stream) | |||
| { | |||
| return Open(stream, FileMode.Open); | |||
| } | |||
| public static Package Open(string path) | |||
| { | |||
| return Open(path, FileMode.OpenOrCreate); | |||
| } | |||
| public static Package Open(Stream stream, FileMode packageMode) | |||
| { | |||
| FileAccess access = packageMode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite; | |||
| return Open(stream, packageMode, access); | |||
| } | |||
| public static Package Open(string path, FileMode packageMode) | |||
| { | |||
| return Open(path, packageMode, FileAccess.ReadWrite); | |||
| } | |||
| public static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess) | |||
| { | |||
| return Open(stream, packageMode, packageAccess, false); | |||
| } | |||
| private static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream) | |||
| { | |||
| return OpenCore(stream, packageMode, packageAccess, ownsStream); | |||
| } | |||
| public static Package Open(string path, FileMode packageMode, FileAccess packageAccess) | |||
| { | |||
| return Open(path, packageMode, packageAccess, FileShare.None); | |||
| } | |||
| public static Package Open(string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare) | |||
| { | |||
| if (packageShare != FileShare.Read && packageShare != FileShare.None) | |||
| throw new NotSupportedException("FileShare.Read and FileShare.None are the only supported options"); | |||
| FileInfo info = new FileInfo(path); | |||
| // Bug - MS.NET appears to test for FileAccess.ReadWrite, not FileAccess.Write | |||
| if (packageAccess != FileAccess.ReadWrite && !info.Exists) | |||
| throw new ArgumentException("packageAccess", "Cannot create stream with FileAccess.Read"); | |||
| if (info.Exists && packageMode == FileMode.OpenOrCreate && info.Length == 0) | |||
| throw new FileFormatException("Stream length cannot be zero with FileMode.Open"); | |||
| Stream s = File.Open(path, packageMode, packageAccess, packageShare); | |||
| return Open(s, packageMode, packageAccess, true); | |||
| } | |||
| private static Package OpenCore(Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream) | |||
| { | |||
| if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead) | |||
| throw new IOException("Stream does not support reading"); | |||
| if ((packageAccess & FileAccess.Write) == FileAccess.Write && !stream.CanWrite) | |||
| throw new IOException("Stream does not support reading"); | |||
| if (!stream.CanSeek) | |||
| throw new ArgumentException("stream", "Stream must support seeking"); | |||
| if (packageMode == FileMode.Open && stream.Length == 0) | |||
| throw new FileFormatException("Stream length cannot be zero with FileMode.Open"); | |||
| if (packageMode == FileMode.CreateNew && stream.Length > 0) | |||
| throw new IOException("Cannot use CreateNew when stream contains data"); | |||
| if (packageMode == FileMode.Append || packageMode == FileMode.Truncate) | |||
| { | |||
| if (stream.CanWrite) | |||
| throw new NotSupportedException(string.Format("PackageMode.{0} is not supported", packageMode)); | |||
| else | |||
| throw new IOException(string.Format("PackageMode.{0} is not supported", packageMode)); | |||
| } | |||
| return new ZipPackage(packageAccess, ownsStream, stream); | |||
| } | |||
| public virtual bool PartExists(Uri partUri) | |||
| { | |||
| return GetPart(partUri) != null; | |||
| } | |||
| public bool RelationshipExists(string id) | |||
| { | |||
| return Relationships.ContainsKey(id); | |||
| } | |||
| internal static void WriteRelationships(Dictionary<string, PackageRelationship> relationships, Stream stream) | |||
| { | |||
| XmlDocument doc = new XmlDocument(); | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| manager.AddNamespace("rel", RelationshipNamespace); | |||
| doc.AppendChild(doc.CreateNode(XmlNodeType.XmlDeclaration, "", "")); | |||
| XmlNode root = doc.CreateNode(XmlNodeType.Element, "Relationships", RelationshipNamespace); | |||
| doc.AppendChild(root); | |||
| foreach (PackageRelationship relationship in relationships.Values) | |||
| { | |||
| XmlNode node = doc.CreateNode(XmlNodeType.Element, "Relationship", RelationshipNamespace); | |||
| XmlAttribute idAtt = doc.CreateAttribute("Id"); | |||
| idAtt.Value = relationship.Id; | |||
| node.Attributes.Append(idAtt); | |||
| XmlAttribute targetAtt = doc.CreateAttribute("Target"); | |||
| targetAtt.Value = relationship.TargetUri.ToString(); | |||
| node.Attributes.Append(targetAtt); | |||
| if (relationship.TargetMode != TargetMode.Internal) | |||
| { | |||
| XmlAttribute modeAtt = doc.CreateAttribute("TargetMode"); | |||
| modeAtt.Value = relationship.TargetMode.ToString(); | |||
| node.Attributes.Append(modeAtt); | |||
| } | |||
| XmlAttribute typeAtt = doc.CreateAttribute("Type"); | |||
| typeAtt.Value = relationship.RelationshipType; | |||
| node.Attributes.Append(typeAtt); | |||
| root.AppendChild(node); | |||
| } | |||
| using (XmlTextWriter writer = new XmlTextWriter(stream, System.Text.Encoding.UTF8)) | |||
| doc.WriteTo(writer); | |||
| } | |||
| } | |||
| internal class FileFormatException : Exception | |||
| { | |||
| public FileFormatException(string message): base(message) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,257 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using System.Xml; | |||
| //using DocumentFormat.OpenXml.Packaging; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public abstract class PackagePart | |||
| { | |||
| string contentType; | |||
| internal bool IsRelationship { get; set; } | |||
| int relationshipId; | |||
| Dictionary<string, PackageRelationship> relationships; | |||
| PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection(); | |||
| Dictionary<string, PackageRelationship> Relationships | |||
| { | |||
| get | |||
| { | |||
| if (relationships == null) | |||
| { | |||
| relationships = new Dictionary<string, PackageRelationship>(StringComparer.OrdinalIgnoreCase); | |||
| if (Package.PartExists(RelationshipsPartUri)) | |||
| using (Stream s = Package.GetPart(RelationshipsPartUri).GetStream()) | |||
| LoadRelationships(relationships, s); | |||
| } | |||
| return relationships; | |||
| } | |||
| } | |||
| Stream PartStream { get; set; } | |||
| internal Uri RelationshipsPartUri | |||
| { | |||
| get; | |||
| set; | |||
| } | |||
| protected PackagePart(Package package, Uri partUri) | |||
| : this(package, partUri, null) | |||
| { | |||
| } | |||
| protected internal PackagePart(Package package, Uri partUri, string contentType) | |||
| : this(package, partUri, contentType, CompressionOption.Normal) | |||
| { | |||
| } | |||
| protected internal PackagePart(Package package, Uri partUri, string contentType, CompressionOption compressionOption) | |||
| { | |||
| Check.Package(package); | |||
| Check.PartUri(partUri); | |||
| Check.ContentTypeIsValid(contentType); | |||
| Package = package; | |||
| Uri = partUri; | |||
| ContentType = contentType; | |||
| CompressionOption = compressionOption; | |||
| RelationshipsPartUri = PackUriHelper.GetRelationshipPartUri(Uri); | |||
| } | |||
| public CompressionOption CompressionOption | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| public string ContentType | |||
| { | |||
| get | |||
| { | |||
| if (contentType == null && (contentType = GetContentTypeCore()) == null) | |||
| throw new NotSupportedException("If contentType is not supplied in the constructor, GetContentTypeCore must be overridden"); | |||
| return contentType; | |||
| } | |||
| private set | |||
| { | |||
| contentType = value; | |||
| } | |||
| } | |||
| public Package Package | |||
| { | |||
| get; | |||
| internal set; | |||
| } | |||
| public Uri Uri | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| private void CheckIsRelationship() | |||
| { | |||
| if (IsRelationship) | |||
| throw new InvalidOperationException("A relationship cannot have relationships to other parts"); | |||
| } | |||
| public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType) | |||
| { | |||
| return CreateRelationship(targetUri, targetMode, relationshipType, null); | |||
| } | |||
| public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, string id) | |||
| { | |||
| return CreateRelationship(targetUri, targetMode, relationshipType, id, false); | |||
| } | |||
| private PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading) | |||
| { | |||
| //Package.CheckIsReadOnly(); | |||
| Check.TargetUri(targetUri); | |||
| Check.RelationshipTypeIsValid(relationshipType); | |||
| Check.IdIsValid(id); | |||
| if (id == null) | |||
| id = NextId(); | |||
| if (Relationships.ContainsKey(id)) | |||
| throw new XmlException("A relationship with this ID already exists"); | |||
| PackageRelationship r = new PackageRelationship(id, Package, relationshipType, Uri, targetMode, targetUri); | |||
| Relationships.Add(r.Id, r); | |||
| if (!loading) | |||
| WriteRelationships(); | |||
| return r; | |||
| } | |||
| public void DeleteRelationship(string id) | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| CheckIsRelationship(); | |||
| Relationships.Remove(id); | |||
| WriteRelationships(); | |||
| } | |||
| void LoadRelationships(Dictionary<string, PackageRelationship> relationships, Stream stream) | |||
| { | |||
| XmlDocument doc = new XmlDocument(); | |||
| doc.Load(stream); | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| manager.AddNamespace("rel", Package.RelationshipNamespace); | |||
| foreach (XmlNode node in doc.SelectNodes("/rel:Relationships/*", manager)) | |||
| { | |||
| TargetMode mode = TargetMode.Internal; | |||
| if (node.Attributes["TargetMode"] != null) | |||
| mode = (TargetMode)Enum.Parse(typeof(TargetMode), node.Attributes["TargetMode"].Value); | |||
| CreateRelationship(new Uri(node.Attributes["Target"].Value.ToString(), UriKind.RelativeOrAbsolute), | |||
| mode, | |||
| node.Attributes["Type"].Value.ToString(), | |||
| node.Attributes["Id"].Value.ToString(), | |||
| true); | |||
| } | |||
| } | |||
| public bool RelationshipExists(string id) | |||
| { | |||
| CheckIsRelationship(); | |||
| return Relationships.ContainsKey(id); | |||
| } | |||
| public PackageRelationship GetRelationship(string id) | |||
| { | |||
| CheckIsRelationship(); | |||
| return Relationships[id]; | |||
| } | |||
| public PackageRelationshipCollection GetRelationships() | |||
| { | |||
| CheckIsRelationship(); | |||
| relationshipsCollection.Relationships.Clear(); | |||
| relationshipsCollection.Relationships.AddRange(Relationships.Values); | |||
| return relationshipsCollection; | |||
| } | |||
| public PackageRelationshipCollection GetRelationshipsByType(string relationshipType) | |||
| { | |||
| CheckIsRelationship(); | |||
| PackageRelationshipCollection collection = new PackageRelationshipCollection(); | |||
| foreach (PackageRelationship r in Relationships.Values) | |||
| if (r.RelationshipType == relationshipType) | |||
| collection.Relationships.Add(r); | |||
| return collection; | |||
| } | |||
| public Stream GetStream() | |||
| { | |||
| return GetStream(Package.FileOpenAccess == FileAccess.Read && !IsRelationship ? FileMode.Open : FileMode.OpenOrCreate); | |||
| } | |||
| public Stream GetStream(FileMode mode) | |||
| { | |||
| return GetStream(mode, IsRelationship ? FileAccess.ReadWrite : Package.FileOpenAccess); | |||
| } | |||
| public Stream GetStream(FileMode mode, FileAccess access) | |||
| { | |||
| bool notAllowed = mode == FileMode.Append || mode == FileMode.CreateNew || mode == FileMode.Truncate; | |||
| if (access != FileAccess.Read && notAllowed) | |||
| throw new ArgumentException(string.Format(string.Format("FileMode '{0}' not supported", mode))); | |||
| if (access == FileAccess.Read && (notAllowed || mode == FileMode.Create)) | |||
| throw new IOException(string.Format("FileMode '{0}' not allowed on a readonly stream", mode)); | |||
| return GetStreamCore(mode, access); | |||
| } | |||
| protected abstract Stream GetStreamCore(FileMode mode, FileAccess access); | |||
| protected virtual string GetContentTypeCore() | |||
| { | |||
| return null; | |||
| } | |||
| private string NextId() | |||
| { | |||
| while (true) | |||
| { | |||
| string s = "Re" + relationshipId.ToString(); | |||
| if (!RelationshipExists(s)) | |||
| return s; | |||
| relationshipId++; | |||
| } | |||
| } | |||
| void WriteRelationships() | |||
| { | |||
| bool exists = Package.PartExists(RelationshipsPartUri); | |||
| if (exists && Relationships.Count == 0) | |||
| { | |||
| Package.DeletePart(RelationshipsPartUri); | |||
| return; | |||
| } | |||
| if (!exists) | |||
| { | |||
| PackagePart part = Package.CreatePart(RelationshipsPartUri, Package.RelationshipContentType); | |||
| part.IsRelationship = true; | |||
| } | |||
| using (Stream s = Package.GetPart(RelationshipsPartUri).GetStream()) | |||
| Package.WriteRelationships(Relationships, s); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| using System; | |||
| using System.Collections; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public class PackagePartCollection : IEnumerable<PackagePart>, IEnumerable | |||
| { | |||
| internal List<PackagePart> Parts { get; private set; } | |||
| internal PackagePartCollection() | |||
| { | |||
| Parts = new List<PackagePart>(); | |||
| } | |||
| public IEnumerator<PackagePart> GetEnumerator() | |||
| { | |||
| return Parts.GetEnumerator(); | |||
| } | |||
| IEnumerator IEnumerable.GetEnumerator() | |||
| { | |||
| return Parts.GetEnumerator(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,91 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using System.Xml; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public abstract class PackageProperties : IDisposable | |||
| { | |||
| internal const string NSPackageProperties = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; | |||
| internal const string NSPackagePropertiesRelation = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; | |||
| internal const string PackagePropertiesContentType = "application/vnd.openxmlformats-package.core-properties+xml"; | |||
| static int uuid; | |||
| protected PackageProperties() | |||
| { | |||
| } | |||
| public abstract string Category { get; set; } | |||
| public abstract string ContentStatus { get; set; } | |||
| public abstract string ContentType { get; set; } | |||
| public abstract DateTime? Created { get; set; } | |||
| public abstract string Creator { get; set; } | |||
| public abstract string Description { get; set; } | |||
| public abstract string Identifier { get; set; } | |||
| public abstract string Keywords { get; set; } | |||
| public abstract string Language { get; set; } | |||
| public abstract string LastModifiedBy { get; set; } | |||
| public abstract DateTime? LastPrinted { get; set; } | |||
| public abstract DateTime? Modified { get; set; } | |||
| internal Package Package { get; set; } | |||
| internal PackagePart Part { get; set; } | |||
| public abstract string Revision { get; set; } | |||
| public abstract string Subject { get; set; } | |||
| public abstract string Title { get; set; } | |||
| public abstract string Version { get; set; } | |||
| public void Dispose() | |||
| { | |||
| Dispose(true); | |||
| } | |||
| protected virtual void Dispose(bool disposing) | |||
| { | |||
| // Nothing | |||
| } | |||
| internal void Flush() | |||
| { | |||
| using (MemoryStream temp = new MemoryStream()) | |||
| { | |||
| using (XmlTextWriter writer = new XmlTextWriter(temp, System.Text.Encoding.UTF8)) | |||
| { | |||
| WriteTo(writer); | |||
| writer.Flush(); | |||
| if (temp.Length == 0) | |||
| return; | |||
| } | |||
| } | |||
| if (Part == null) | |||
| { | |||
| int id = System.Threading.Interlocked.Increment(ref uuid); | |||
| Uri uri = new Uri(string.Format("/package/services/metadata/core-properties/{0}.psmdcp", id), UriKind.Relative); | |||
| Part = Package.CreatePart(uri, PackagePropertiesContentType); | |||
| PackageRelationship rel = Package.CreateRelationship(uri, TargetMode.Internal, NSPackagePropertiesRelation); | |||
| } | |||
| using (Stream s = Part.GetStream(FileMode.Create)) | |||
| using (XmlTextWriter writer = new XmlTextWriter(s, System.Text.Encoding.UTF8)) | |||
| WriteTo(writer); | |||
| } | |||
| internal virtual void LoadFrom(Stream stream) | |||
| { | |||
| } | |||
| internal virtual void WriteTo(XmlTextWriter writer) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,355 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using System.Xml; | |||
| namespace System.IO.Packaging | |||
| { | |||
| class PackagePropertiesPart : PackageProperties | |||
| { | |||
| const string NSDc = "http://purl.org/dc/elements/1.1/"; | |||
| const string NSDcTerms = "http://purl.org/dc/terms/"; | |||
| const string NSXsi = "http://www.w3.org/2001/XMLSchema-instance"; | |||
| string category; | |||
| string contentStatus; | |||
| string contentType; | |||
| DateTime? created; | |||
| string creator; | |||
| string description; | |||
| string identifier; | |||
| string keywords; | |||
| string language; | |||
| string lastModifiedBy; | |||
| DateTime? lastPrinted; | |||
| DateTime? modified; | |||
| string revision; | |||
| string subject; | |||
| string title; | |||
| string version; | |||
| public PackagePropertiesPart() | |||
| { | |||
| } | |||
| public override string Category | |||
| { | |||
| get | |||
| { | |||
| return category; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| category = value; | |||
| } | |||
| } | |||
| public override string ContentStatus | |||
| { | |||
| get | |||
| { | |||
| return contentStatus; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| contentStatus = value; | |||
| } | |||
| } | |||
| public override string ContentType | |||
| { | |||
| get | |||
| { | |||
| return contentType; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| contentType = value; | |||
| } | |||
| } | |||
| public override DateTime? Created | |||
| { | |||
| get | |||
| { | |||
| return created; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| created = value; | |||
| } | |||
| } | |||
| public override string Creator | |||
| { | |||
| get | |||
| { | |||
| return creator; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| creator = value; | |||
| } | |||
| } | |||
| public override string Description | |||
| { | |||
| get | |||
| { | |||
| return description; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| description = value; | |||
| } | |||
| } | |||
| public override string Identifier | |||
| { | |||
| get | |||
| { | |||
| return identifier; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| identifier = value; | |||
| } | |||
| } | |||
| public override string Keywords | |||
| { | |||
| get | |||
| { | |||
| return keywords; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| keywords = value; | |||
| } | |||
| } | |||
| public override string Language | |||
| { | |||
| get | |||
| { | |||
| return language; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| language = value; | |||
| } | |||
| } | |||
| public override string LastModifiedBy | |||
| { | |||
| get | |||
| { | |||
| return lastModifiedBy; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| lastModifiedBy = value; | |||
| } | |||
| } | |||
| public override DateTime? LastPrinted | |||
| { | |||
| get | |||
| { | |||
| return lastPrinted; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| lastPrinted = value; | |||
| } | |||
| } | |||
| public override DateTime? Modified | |||
| { | |||
| get | |||
| { | |||
| return modified; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| modified = value; | |||
| } | |||
| } | |||
| public override string Revision | |||
| { | |||
| get | |||
| { | |||
| return revision; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| revision = value; | |||
| } | |||
| } | |||
| public override string Subject | |||
| { | |||
| get | |||
| { | |||
| return subject; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| subject = value; | |||
| } | |||
| } | |||
| public override string Title | |||
| { | |||
| get | |||
| { | |||
| return title; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| title = value; | |||
| } | |||
| } | |||
| public override string Version | |||
| { | |||
| get | |||
| { | |||
| return version; | |||
| } | |||
| set | |||
| { | |||
| Package.CheckIsReadOnly(); | |||
| version = value; | |||
| } | |||
| } | |||
| internal override void LoadFrom(Stream stream) | |||
| { | |||
| if (stream.Length == 0) | |||
| return; | |||
| XmlDocument doc = new XmlDocument(); | |||
| doc.Load(stream); | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| manager.AddNamespace("prop", NSPackageProperties); | |||
| manager.AddNamespace("dc", NSDc); | |||
| manager.AddNamespace("dcterms", NSDcTerms); | |||
| manager.AddNamespace("xsi", NSXsi); | |||
| XmlNode node; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:category", manager)) != null) | |||
| category = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:contentStatus", manager)) != null) | |||
| contentStatus = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:contentType", manager)) != null) | |||
| contentType = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dcterms:created", manager)) != null) | |||
| created = DateTime.Parse(node.InnerXml); | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dc:creator", manager)) != null) | |||
| creator = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dc:description", manager)) != null) | |||
| description = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dc:identifier", manager)) != null) | |||
| identifier = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:keywords", manager)) != null) | |||
| keywords = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dc:language", manager)) != null) | |||
| language = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:lastModifiedBy", manager)) != null) | |||
| lastModifiedBy = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:lastPrinted", manager)) != null) | |||
| lastPrinted = DateTime.Parse(node.InnerXml); | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dcterms:modified", manager)) != null) | |||
| modified = DateTime.Parse(node.InnerXml); | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:revision", manager)) != null) | |||
| revision = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dc:subject", manager)) != null) | |||
| subject = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/dc:title", manager)) != null) | |||
| title = node.InnerXml; | |||
| if ((node = doc.SelectSingleNode("prop:coreProperties/prop:version", manager)) != null) | |||
| version = node.InnerXml; | |||
| } | |||
| internal override void WriteTo(XmlTextWriter writer) | |||
| { | |||
| XmlDocument doc = new XmlDocument(); | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| manager.AddNamespace("prop", NSPackageProperties); | |||
| manager.AddNamespace("dc", NSDc); | |||
| manager.AddNamespace("dcterms", NSDcTerms); | |||
| manager.AddNamespace("xsi", NSXsi); | |||
| // Create XML declaration | |||
| doc.AppendChild(doc.CreateXmlDeclaration("1.0", "UTF-8", null)); | |||
| // Create root node with required namespace declarations | |||
| XmlNode coreProperties = doc.AppendChild(doc.CreateNode(XmlNodeType.Element, "coreProperties", NSPackageProperties)); | |||
| coreProperties.Attributes.Append(doc.CreateAttribute("xmlns:dc")).Value = NSDc; | |||
| coreProperties.Attributes.Append(doc.CreateAttribute("xmlns:dcterms")).Value = NSDcTerms; | |||
| coreProperties.Attributes.Append(doc.CreateAttribute("xmlns:xsi")).Value = NSXsi; | |||
| // Create the children | |||
| if (Category != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "category", NSPackageProperties)).InnerXml = Category; | |||
| if (ContentStatus != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "contentStatus", NSPackageProperties)).InnerXml = ContentStatus; | |||
| if (ContentType != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "contentType", NSPackageProperties)).InnerXml = ContentType; | |||
| if (Created.HasValue) | |||
| { | |||
| XmlAttribute att = doc.CreateAttribute("xsi", "type", NSXsi); | |||
| att.Value = "dcterms:W3CDTF"; | |||
| XmlNode created = coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dcterms", "created", NSDcTerms)); | |||
| created.Attributes.Append(att); | |||
| created.InnerXml = Created.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss") + "Z"; | |||
| } | |||
| if (Creator != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dc", "creator", NSDc)).InnerXml = Creator; | |||
| if (Description != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dc", "description", NSDc)).InnerXml = Description; | |||
| if (Identifier != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dc", "identifier", NSDc)).InnerXml = Identifier; | |||
| if (Keywords != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "keywords", NSPackageProperties)).InnerXml = Keywords; | |||
| if (Language != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dc", "language", NSDc)).InnerXml = Language; | |||
| if (LastModifiedBy != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "lastModifiedBy", NSPackageProperties)).InnerXml = LastModifiedBy; | |||
| if (LastPrinted.HasValue) | |||
| { | |||
| XmlNode lastPrinted = coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "lastPrinted", NSPackageProperties)); | |||
| lastPrinted.InnerXml = LastPrinted.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss") + "Z"; | |||
| } | |||
| if (Revision != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "revision", NSPackageProperties)).InnerXml = Revision; | |||
| if (Subject != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dc", "subject", NSDc)).InnerXml = Subject; | |||
| if (Title != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dc", "title", NSDc)).InnerXml = Title; | |||
| if (Version != null) | |||
| coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "version", NSPackageProperties)).InnerXml = Version; | |||
| if (Modified.HasValue) | |||
| { | |||
| XmlAttribute att = doc.CreateAttribute("xsi", "type", NSXsi); | |||
| att.Value = "dcterms:W3CDTF"; | |||
| XmlNode modified = coreProperties.AppendChild(doc.CreateNode(XmlNodeType.Element, "dcterms", "modified", NSDcTerms)); | |||
| modified.Attributes.Append(att); | |||
| modified.InnerXml = Modified.Value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss") + "Z"; | |||
| } | |||
| doc.WriteContentTo(writer); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public class PackageRelationship | |||
| { | |||
| internal PackageRelationship(string id, Package package, string relationshipType, | |||
| Uri sourceUri, TargetMode targetMode, Uri targetUri) | |||
| { | |||
| Check.IdIsValid(id); | |||
| Check.Package(package); | |||
| Check.RelationshipTypeIsValid(relationshipType); | |||
| Check.SourceUri(sourceUri); | |||
| Check.TargetUri(targetUri); | |||
| Id = id; | |||
| Package = package; | |||
| RelationshipType = relationshipType; | |||
| SourceUri = sourceUri; | |||
| TargetMode = targetMode; | |||
| TargetUri = targetUri; | |||
| } | |||
| public string Id | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| public Package Package | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| public string RelationshipType | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| public Uri SourceUri | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| public TargetMode TargetMode | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| public Uri TargetUri | |||
| { | |||
| get; | |||
| private set; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| using System; | |||
| using System.Collections; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public class PackageRelationshipCollection : IEnumerable<PackageRelationship>, IEnumerable | |||
| { | |||
| internal List<PackageRelationship> Relationships { get; private set; } | |||
| internal PackageRelationshipCollection() | |||
| { | |||
| Relationships = new List<PackageRelationship>(); | |||
| } | |||
| public IEnumerator<PackageRelationship> GetEnumerator() | |||
| { | |||
| return Relationships.GetEnumerator(); | |||
| } | |||
| IEnumerator IEnumerable.GetEnumerator() | |||
| { | |||
| return GetEnumerator(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public enum TargetMode | |||
| { | |||
| Internal, | |||
| External | |||
| } | |||
| } | |||
| @@ -0,0 +1,266 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using System.Xml; | |||
| using DocX.iOS.Zip; | |||
| namespace System.IO.Packaging | |||
| { | |||
| class UriComparer : IEqualityComparer<Uri> | |||
| { | |||
| public int GetHashCode(Uri uri) | |||
| { | |||
| return 1; | |||
| } | |||
| public bool Equals(Uri x, Uri y) | |||
| { | |||
| return x.OriginalString.Equals(y.OriginalString, StringComparison.OrdinalIgnoreCase); | |||
| } | |||
| } | |||
| public sealed class ZipPackage : Package | |||
| { | |||
| public ZipStorer Archive { get; set; } | |||
| private const string ContentNamespace = "http://schemas.openxmlformats.org/package/2006/content-types"; | |||
| private const string ContentUri = "[Content_Types].xml"; | |||
| bool OwnsStream | |||
| { | |||
| get; | |||
| set; | |||
| } | |||
| Dictionary<Uri, ZipPackagePart> parts; | |||
| internal Dictionary<Uri, MemoryStream> PartStreams = new Dictionary<Uri, MemoryStream>(new UriComparer()); | |||
| internal Stream PackageStream { get; set; } | |||
| Dictionary<Uri, ZipPackagePart> Parts | |||
| { | |||
| get | |||
| { | |||
| if (parts == null) | |||
| LoadParts(); | |||
| return parts; | |||
| } | |||
| } | |||
| internal ZipPackage(FileAccess access, bool ownsStream, Stream stream) | |||
| : base(access) | |||
| { | |||
| OwnsStream = ownsStream; | |||
| PackageStream = stream; | |||
| } | |||
| internal ZipPackage(FileAccess access, bool ownsStream, Stream stream, bool streaming) | |||
| : base(access, streaming) | |||
| { | |||
| OwnsStream = ownsStream; | |||
| PackageStream = stream; | |||
| } | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| foreach (Stream s in PartStreams.Values) | |||
| s.Close(); | |||
| if (Archive != null) // GZE fixed bug where Archive == null | |||
| { | |||
| Archive.Close(); | |||
| } | |||
| base.Dispose(disposing); | |||
| if (OwnsStream) | |||
| PackageStream.Close(); | |||
| } | |||
| protected override void FlushCore() | |||
| { | |||
| // Ensure that all the data has been read out of the package | |||
| // stream already. Otherwise we'll lose data when we recreate the zip | |||
| foreach (ZipPackagePart part in Parts.Values) | |||
| { | |||
| part.GetStream(); | |||
| } | |||
| if (!PackageStream.CanSeek) | |||
| return; | |||
| // Empty the package stream | |||
| PackageStream.Position = 0; | |||
| PackageStream.SetLength(0); | |||
| // Recreate the zip file | |||
| using (ZipStorer archive = ZipStorer.Create(PackageStream, "", false)) | |||
| { | |||
| // Write all the part streams | |||
| foreach (ZipPackagePart part in Parts.Values) | |||
| { | |||
| Stream partStream = part.GetStream(); | |||
| partStream.Seek(0, SeekOrigin.Begin); | |||
| archive.AddStream(ZipStorer.Compression.Deflate, part.Uri.ToString().Substring(1), partStream, | |||
| DateTime.UtcNow, ""); | |||
| } | |||
| using (var ms = new MemoryStream()) | |||
| { | |||
| WriteContentType(ms); | |||
| ms.Seek(0, SeekOrigin.Begin); | |||
| archive.AddStream(ZipStorer.Compression.Deflate, ContentUri, ms, DateTime.UtcNow, ""); | |||
| } | |||
| } | |||
| } | |||
| protected override PackagePart CreatePartCore(Uri partUri, string contentType, CompressionOption compressionOption) | |||
| { | |||
| ZipPackagePart part = new ZipPackagePart(this, partUri, contentType, compressionOption); | |||
| Parts.Add(part.Uri, part); | |||
| return part; | |||
| } | |||
| protected override void DeletePartCore(Uri partUri) | |||
| { | |||
| Parts.Remove(partUri); | |||
| } | |||
| protected override PackagePart GetPartCore(Uri partUri) | |||
| { | |||
| ZipPackagePart part; | |||
| Parts.TryGetValue(partUri, out part); | |||
| return part; | |||
| } | |||
| protected override PackagePart[] GetPartsCore() | |||
| { | |||
| ZipPackagePart[] p = new ZipPackagePart[Parts.Count]; | |||
| Parts.Values.CopyTo(p, 0); | |||
| return p; | |||
| } | |||
| void LoadParts() | |||
| { | |||
| parts = new Dictionary<Uri, ZipPackagePart>(new UriComparer()); | |||
| try | |||
| { | |||
| PackageStream.Seek(0, SeekOrigin.Begin); | |||
| if (Archive == null) | |||
| { | |||
| Archive = ZipStorer.Open(PackageStream, FileAccess.Read, false); | |||
| } | |||
| List<ZipStorer.ZipFileEntry> dir = Archive.ReadCentralDir(); | |||
| // Load the content type map file | |||
| XmlDocument doc = new XmlDocument(); | |||
| var content = dir.FirstOrDefault(x => x.FilenameInZip == ContentUri); | |||
| using (var ms = new MemoryStream()) | |||
| { | |||
| Archive.ExtractFile(content, ms); | |||
| ms.Seek(0, SeekOrigin.Begin); | |||
| doc.Load(ms); | |||
| } | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| manager.AddNamespace("content", ContentNamespace); | |||
| // The file names in the zip archive are not prepended with '/' | |||
| foreach (var file in dir) | |||
| { | |||
| if (file.FilenameInZip.Equals(ContentUri, StringComparison.Ordinal)) | |||
| continue; | |||
| XmlNode node; | |||
| if (file.FilenameInZip == RelationshipUri.ToString().Substring(1)) | |||
| { | |||
| CreatePartCore(RelationshipUri, RelationshipContentType, CompressionOption.Normal); | |||
| continue; | |||
| } | |||
| string xPath = string.Format("/content:Types/content:Override[@PartName='/{0}']", file); | |||
| node = doc.SelectSingleNode(xPath, manager); | |||
| if (node == null) | |||
| { | |||
| string ext = Path.GetExtension(file.FilenameInZip); | |||
| if (ext.StartsWith(".")) | |||
| ext = ext.Substring(1); | |||
| xPath = string.Format("/content:Types/content:Default[@Extension='{0}']", ext); | |||
| node = doc.SelectSingleNode(xPath, manager); | |||
| } | |||
| // What do i do if the node is null? This means some has tampered with the | |||
| // package file manually | |||
| if (node != null) | |||
| CreatePartCore(new Uri("/" + file, UriKind.Relative), node.Attributes["ContentType"].Value, | |||
| CompressionOption.Normal); | |||
| } | |||
| } | |||
| catch | |||
| { | |||
| // The archive is invalid - therefore no parts | |||
| } | |||
| } | |||
| void WriteContentType(Stream s) | |||
| { | |||
| XmlDocument doc = new XmlDocument(); | |||
| XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable); | |||
| Dictionary<string, string> mimes = new Dictionary<string, string>(); | |||
| manager.AddNamespace("content", ContentNamespace); | |||
| doc.AppendChild(doc.CreateNode(XmlNodeType.XmlDeclaration, "", "")); | |||
| XmlNode root = doc.CreateNode(XmlNodeType.Element, "Types", ContentNamespace); | |||
| doc.AppendChild(root); | |||
| foreach (ZipPackagePart part in Parts.Values) | |||
| { | |||
| XmlNode node = null; | |||
| string existingMimeType; | |||
| var extension = Path.GetExtension(part.Uri.OriginalString); | |||
| if (extension.Length > 0) | |||
| extension = extension.Substring(1); | |||
| if (!mimes.TryGetValue(extension, out existingMimeType)) | |||
| { | |||
| node = doc.CreateNode(XmlNodeType.Element, "Default", ContentNamespace); | |||
| XmlAttribute ext = doc.CreateAttribute("Extension"); | |||
| ext.Value = extension; | |||
| node.Attributes.Append(ext); | |||
| mimes[extension] = part.ContentType; | |||
| } | |||
| else if (part.ContentType != existingMimeType) | |||
| { | |||
| node = doc.CreateNode(XmlNodeType.Element, "Override", ContentNamespace); | |||
| XmlAttribute name = doc.CreateAttribute("PartName"); | |||
| name.Value = part.Uri.ToString(); | |||
| node.Attributes.Append(name); | |||
| } | |||
| if (node != null) | |||
| { | |||
| XmlAttribute contentType = doc.CreateAttribute("ContentType"); | |||
| contentType.Value = part.ContentType; | |||
| node.Attributes.Prepend(contentType); | |||
| root.AppendChild(node); | |||
| } | |||
| } | |||
| XmlTextWriter writer = new XmlTextWriter(s, System.Text.Encoding.UTF8); | |||
| doc.WriteTo(writer); | |||
| writer.Flush(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using DocX.iOS.Zip; | |||
| namespace System.IO.Packaging | |||
| { | |||
| public sealed class ZipPackagePart : PackagePart | |||
| { | |||
| new ZipPackage Package | |||
| { | |||
| get { return (ZipPackage)base.Package; } | |||
| } | |||
| internal ZipPackagePart(Package package, Uri partUri) | |||
| : base(package, partUri) | |||
| { | |||
| } | |||
| internal ZipPackagePart(Package package, Uri partUri, string contentType) | |||
| : base(package, partUri, contentType) | |||
| { | |||
| } | |||
| internal ZipPackagePart(Package package, Uri partUri, string contentType, CompressionOption compressionOption) | |||
| : base(package, partUri, contentType, compressionOption) | |||
| { | |||
| } | |||
| protected override Stream GetStreamCore(FileMode mode, FileAccess access) | |||
| { | |||
| ZipPartStream zps; | |||
| MemoryStream stream; | |||
| if (Package.PartStreams.TryGetValue(Uri, out stream)) | |||
| { | |||
| //zps = new ZipPartStream(Package, stream, access); | |||
| if (mode == FileMode.Create) | |||
| stream.SetLength(0); | |||
| return new ZipPartStream(Package, stream, access); | |||
| } | |||
| stream = new MemoryStream(); | |||
| try | |||
| { | |||
| if (Package.Archive == null) | |||
| { | |||
| Package.Archive = ZipStorer.Open(Package.PackageStream, access, false); | |||
| } | |||
| List<ZipStorer.ZipFileEntry> dir = Package.Archive.ReadCentralDir(); | |||
| foreach (ZipStorer.ZipFileEntry entry in dir) | |||
| { | |||
| if (entry.FilenameInZip != Uri.ToString().Substring(1)) | |||
| continue; | |||
| Package.Archive.ExtractFile(entry, stream); | |||
| } | |||
| } | |||
| catch | |||
| { | |||
| // The zipfile is invalid, so just create the file | |||
| // as if it didn't exist | |||
| stream.SetLength(0); | |||
| } | |||
| Package.PartStreams.Add(Uri, stream); | |||
| if (mode == FileMode.Create) | |||
| stream.SetLength(0); | |||
| return new ZipPartStream(Package, stream, access); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace System.IO.Packaging | |||
| { | |||
| internal class ZipPartStream : Stream | |||
| { | |||
| public MemoryStream BaseStream { get; private set; } | |||
| public ZipPackage Package { get; private set; } | |||
| public override bool CanRead | |||
| { | |||
| get | |||
| { | |||
| return BaseStream.CanRead; | |||
| } | |||
| } | |||
| public override bool CanSeek | |||
| { | |||
| get | |||
| { | |||
| return BaseStream.CanSeek; | |||
| } | |||
| } | |||
| public override bool CanTimeout | |||
| { | |||
| get { return BaseStream.CanTimeout; } | |||
| } | |||
| public override bool CanWrite | |||
| { | |||
| get { return Writeable; } | |||
| } | |||
| public override long Length | |||
| { | |||
| get { return BaseStream.Length; } | |||
| } | |||
| public override long Position | |||
| { | |||
| get; | |||
| set; | |||
| } | |||
| public bool Writeable | |||
| { | |||
| get; | |||
| set; | |||
| } | |||
| public override int WriteTimeout | |||
| { | |||
| get | |||
| { | |||
| return BaseStream.WriteTimeout; | |||
| } | |||
| set | |||
| { | |||
| BaseStream.WriteTimeout = value; | |||
| } | |||
| } | |||
| public override int ReadTimeout | |||
| { | |||
| get | |||
| { | |||
| return BaseStream.ReadTimeout; | |||
| } | |||
| set | |||
| { | |||
| BaseStream.ReadTimeout = value; | |||
| } | |||
| } | |||
| public ZipPartStream(ZipPackage package, MemoryStream stream, FileAccess access) | |||
| { | |||
| BaseStream = stream; | |||
| Package = package; | |||
| Writeable = access != FileAccess.Read; | |||
| } | |||
| public override void Flush() | |||
| { | |||
| // If the user flushes any of the part streams, | |||
| // we need to flush the entire package | |||
| // FIXME: Ensure that this actually happens with a testcase | |||
| // ...if possible | |||
| // Package.Flush(); | |||
| } | |||
| public override int Read(byte[] buffer, int offset, int count) | |||
| { | |||
| Seek(Position, SeekOrigin.Begin); | |||
| int read = BaseStream.Read(buffer, offset, count); | |||
| Position += read; | |||
| return read; | |||
| } | |||
| public override long Seek(long offset, SeekOrigin origin) | |||
| { | |||
| Position = BaseStream.Seek(offset, origin); | |||
| return Position; | |||
| } | |||
| public override void SetLength(long value) | |||
| { | |||
| if (!CanWrite) | |||
| throw new InvalidOperationException("Stream is not writeable"); | |||
| BaseStream.SetLength(value); | |||
| } | |||
| public override void Write(byte[] buffer, int offset, int count) | |||
| { | |||
| if (!CanWrite) | |||
| throw new InvalidOperationException("Stream is not writeable"); | |||
| Seek(Position, SeekOrigin.Begin); | |||
| BaseStream.Write(buffer, offset, count); | |||
| Position += count; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,758 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.IO; | |||
| using System.IO.Compression; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace DocX.iOS.Zip | |||
| { | |||
| /// <summary> | |||
| /// Unique class for compression/decompression file. Represents a Zip file. | |||
| /// </summary> | |||
| public class ZipStorer : IDisposable | |||
| { | |||
| /// <summary> | |||
| /// Compression method enumeration | |||
| /// </summary> | |||
| public enum Compression : ushort | |||
| { | |||
| /// <summary>Uncompressed storage</summary> | |||
| Store = 0, | |||
| /// <summary>Deflate compression method</summary> | |||
| Deflate = 8 | |||
| } | |||
| /// <summary> | |||
| /// Represents an entry in Zip file directory | |||
| /// </summary> | |||
| public struct ZipFileEntry | |||
| { | |||
| /// <summary>Compression method</summary> | |||
| public Compression Method; | |||
| /// <summary>Full path and filename as stored in Zip</summary> | |||
| public string FilenameInZip; | |||
| /// <summary>Original file size</summary> | |||
| public uint FileSize; | |||
| /// <summary>Compressed file size</summary> | |||
| public uint CompressedSize; | |||
| /// <summary>Offset of header information inside Zip storage</summary> | |||
| public uint HeaderOffset; | |||
| /// <summary>Offset of file inside Zip storage</summary> | |||
| public uint FileOffset; | |||
| /// <summary>Size of header information</summary> | |||
| public uint HeaderSize; | |||
| /// <summary>32-bit checksum of entire file</summary> | |||
| public uint Crc32; | |||
| /// <summary>Last modification time of file</summary> | |||
| public DateTime ModifyTime; | |||
| /// <summary>User comment for file</summary> | |||
| public string Comment; | |||
| /// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary> | |||
| public bool EncodeUTF8; | |||
| /// <summary>Overriden method</summary> | |||
| /// <returns>Filename in Zip</returns> | |||
| public override string ToString() | |||
| { | |||
| return this.FilenameInZip; | |||
| } | |||
| } | |||
| #region Public fields | |||
| /// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary> | |||
| public bool EncodeUTF8 = false; | |||
| /// <summary>Force deflate algotithm even if it inflates the stored file. Off by default.</summary> | |||
| public bool ForceDeflating = false; | |||
| #endregion | |||
| #region Private fields | |||
| // List of files to store | |||
| private List<ZipFileEntry> Files = new List<ZipFileEntry>(); | |||
| // Filename of storage file | |||
| private string FileName; | |||
| // Stream object of storage file | |||
| private Stream ZipFileStream; | |||
| private bool OwnsStream = true; | |||
| // General comment | |||
| private string Comment = ""; | |||
| // Central dir image | |||
| private byte[] CentralDirImage = null; | |||
| // Existing files in zip | |||
| private ushort ExistingFiles = 0; | |||
| // File access for Open method | |||
| private FileAccess Access; | |||
| // Static CRC32 Table | |||
| private static UInt32[] CrcTable = null; | |||
| // Default filename encoder | |||
| private static Encoding DefaultEncoding = Encoding.GetEncoding(437); | |||
| #endregion | |||
| #region Public methods | |||
| // Static constructor. Just invoked once in order to create the CRC32 lookup table. | |||
| static ZipStorer() | |||
| { | |||
| // Generate CRC32 table | |||
| CrcTable = new UInt32[256]; | |||
| for (int i = 0; i < CrcTable.Length; i++) | |||
| { | |||
| UInt32 c = (UInt32)i; | |||
| for (int j = 0; j < 8; j++) | |||
| { | |||
| if ((c & 1) != 0) | |||
| c = 3988292384 ^ (c >> 1); | |||
| else | |||
| c >>= 1; | |||
| } | |||
| CrcTable[i] = c; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Method to create a new storage file | |||
| /// </summary> | |||
| /// <param name="_filename">Full path of Zip file to create</param> | |||
| /// <param name="_comment">General comment for Zip file</param> | |||
| /// <returns>A valid ZipStorer object</returns> | |||
| public static ZipStorer Create(string _filename, string _comment) | |||
| { | |||
| Stream stream = new FileStream(_filename, FileMode.Create, FileAccess.ReadWrite); | |||
| ZipStorer zip = Create(stream, _comment, true); | |||
| zip.Comment = _comment; | |||
| zip.FileName = _filename; | |||
| return zip; | |||
| } | |||
| /// <summary> | |||
| /// Method to create a new zip storage in a stream | |||
| /// </summary> | |||
| /// <param name="_stream"></param> | |||
| /// <param name="_comment"></param> | |||
| /// <returns>A valid ZipStorer object</returns> | |||
| public static ZipStorer Create(Stream _stream, string _comment, bool _ownsStream = false) | |||
| { | |||
| ZipStorer zip = new ZipStorer(); | |||
| zip.Comment = _comment; | |||
| zip.ZipFileStream = _stream; | |||
| zip.OwnsStream = _ownsStream; | |||
| zip.Access = FileAccess.Write; | |||
| return zip; | |||
| } | |||
| /// <summary> | |||
| /// Method to open an existing storage file | |||
| /// </summary> | |||
| /// <param name="_filename">Full path of Zip file to open</param> | |||
| /// <param name="_access">File access mode as used in FileStream constructor</param> | |||
| /// <returns>A valid ZipStorer object</returns> | |||
| public static ZipStorer Open(string _filename, FileAccess _access) | |||
| { | |||
| Stream stream = (Stream)new FileStream(_filename, FileMode.Open, _access == FileAccess.Read ? FileAccess.Read : FileAccess.ReadWrite); | |||
| ZipStorer zip = Open(stream, _access, true); | |||
| zip.FileName = _filename; | |||
| return zip; | |||
| } | |||
| /// <summary> | |||
| /// Method to open an existing storage from stream | |||
| /// </summary> | |||
| /// <param name="_stream">Already opened stream with zip contents</param> | |||
| /// <param name="_access">File access mode for stream operations</param> | |||
| /// <returns>A valid ZipStorer object</returns> | |||
| public static ZipStorer Open(Stream _stream, FileAccess _access, bool _ownsStream = false) | |||
| { | |||
| if (!_stream.CanSeek && _access != FileAccess.Read) | |||
| throw new InvalidOperationException("Stream cannot seek"); | |||
| ZipStorer zip = new ZipStorer(); | |||
| //zip.FileName = _filename; | |||
| zip.ZipFileStream = _stream; | |||
| zip.OwnsStream = _ownsStream; | |||
| zip.Access = _access; | |||
| if (zip.ReadFileInfo()) | |||
| return zip; | |||
| throw new System.IO.InvalidDataException(); | |||
| } | |||
| /// <summary> | |||
| /// Add full contents of a file into the Zip storage | |||
| /// </summary> | |||
| /// <param name="_method">Compression method</param> | |||
| /// <param name="_pathname">Full path of file to add to Zip storage</param> | |||
| /// <param name="_filenameInZip">Filename and path as desired in Zip directory</param> | |||
| /// <param name="_comment">Comment for stored file</param> | |||
| public void AddFile(Compression _method, string _pathname, string _filenameInZip, string _comment) | |||
| { | |||
| if (Access == FileAccess.Read) | |||
| throw new InvalidOperationException("Writing is not alowed"); | |||
| FileStream stream = new FileStream(_pathname, FileMode.Open, FileAccess.Read); | |||
| AddStream(_method, _filenameInZip, stream, File.GetLastWriteTime(_pathname), _comment); | |||
| stream.Close(); | |||
| } | |||
| /// <summary> | |||
| /// Add full contents of a stream into the Zip storage | |||
| /// </summary> | |||
| /// <param name="_method">Compression method</param> | |||
| /// <param name="_filenameInZip">Filename and path as desired in Zip directory</param> | |||
| /// <param name="_source">Stream object containing the data to store in Zip</param> | |||
| /// <param name="_modTime">Modification time of the data to store</param> | |||
| /// <param name="_comment">Comment for stored file</param> | |||
| public void AddStream(Compression _method, string _filenameInZip, Stream _source, DateTime _modTime, string _comment) | |||
| { | |||
| if (Access == FileAccess.Read) | |||
| throw new InvalidOperationException("Writing is not alowed"); | |||
| long offset; | |||
| if (this.Files.Count == 0) | |||
| offset = 0; | |||
| else | |||
| { | |||
| ZipFileEntry last = this.Files[this.Files.Count - 1]; | |||
| offset = last.HeaderOffset + last.HeaderSize; | |||
| } | |||
| // Prepare the fileinfo | |||
| ZipFileEntry zfe = new ZipFileEntry(); | |||
| zfe.Method = _method; | |||
| zfe.EncodeUTF8 = this.EncodeUTF8; | |||
| zfe.FilenameInZip = NormalizedFilename(_filenameInZip); | |||
| zfe.Comment = (_comment == null ? "" : _comment); | |||
| // Even though we write the header now, it will have to be rewritten, since we don't know compressed size or crc. | |||
| zfe.Crc32 = 0; // to be updated later | |||
| zfe.HeaderOffset = (uint)this.ZipFileStream.Position; // offset within file of the start of this local record | |||
| zfe.ModifyTime = _modTime; | |||
| // Write local header | |||
| WriteLocalHeader(ref zfe); | |||
| zfe.FileOffset = (uint)this.ZipFileStream.Position; | |||
| // Write file to zip (store) | |||
| Store(ref zfe, _source); | |||
| _source.Close(); | |||
| this.UpdateCrcAndSizes(ref zfe); | |||
| Files.Add(zfe); | |||
| } | |||
| /// <summary> | |||
| /// Updates central directory (if pertinent) and close the Zip storage | |||
| /// </summary> | |||
| /// <remarks>This is a required step, unless automatic dispose is used</remarks> | |||
| public void Close() | |||
| { | |||
| if (this.Access != FileAccess.Read) | |||
| { | |||
| uint centralOffset = (uint)this.ZipFileStream.Position; | |||
| uint centralSize = 0; | |||
| if (this.CentralDirImage != null) | |||
| this.ZipFileStream.Write(CentralDirImage, 0, CentralDirImage.Length); | |||
| for (int i = 0; i < Files.Count; i++) | |||
| { | |||
| long pos = this.ZipFileStream.Position; | |||
| this.WriteCentralDirRecord(Files[i]); | |||
| centralSize += (uint)(this.ZipFileStream.Position - pos); | |||
| } | |||
| if (this.CentralDirImage != null) | |||
| this.WriteEndRecord(centralSize + (uint)CentralDirImage.Length, centralOffset); | |||
| else | |||
| this.WriteEndRecord(centralSize, centralOffset); | |||
| } | |||
| if (this.ZipFileStream != null && ZipFileStream.CanSeek) | |||
| { | |||
| this.ZipFileStream.Flush(); | |||
| // GZE added ownsStream, to prevent premature closure | |||
| if (this.OwnsStream) | |||
| { | |||
| this.ZipFileStream.Dispose(); | |||
| } | |||
| this.ZipFileStream = null; | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Read all the file records in the central directory | |||
| /// </summary> | |||
| /// <returns>List of all entries in directory</returns> | |||
| public List<ZipFileEntry> ReadCentralDir() | |||
| { | |||
| if (this.CentralDirImage == null) | |||
| throw new InvalidOperationException("Central directory currently does not exist"); | |||
| List<ZipFileEntry> result = new List<ZipFileEntry>(); | |||
| for (int pointer = 0; pointer < this.CentralDirImage.Length; ) | |||
| { | |||
| uint signature = BitConverter.ToUInt32(CentralDirImage, pointer); | |||
| if (signature != 0x02014b50) | |||
| break; | |||
| bool encodeUTF8 = (BitConverter.ToUInt16(CentralDirImage, pointer + 8) & 0x0800) != 0; | |||
| ushort method = BitConverter.ToUInt16(CentralDirImage, pointer + 10); | |||
| uint modifyTime = BitConverter.ToUInt32(CentralDirImage, pointer + 12); | |||
| uint crc32 = BitConverter.ToUInt32(CentralDirImage, pointer + 16); | |||
| uint comprSize = BitConverter.ToUInt32(CentralDirImage, pointer + 20); | |||
| uint fileSize = BitConverter.ToUInt32(CentralDirImage, pointer + 24); | |||
| ushort filenameSize = BitConverter.ToUInt16(CentralDirImage, pointer + 28); | |||
| ushort extraSize = BitConverter.ToUInt16(CentralDirImage, pointer + 30); | |||
| ushort commentSize = BitConverter.ToUInt16(CentralDirImage, pointer + 32); | |||
| uint headerOffset = BitConverter.ToUInt32(CentralDirImage, pointer + 42); | |||
| uint headerSize = (uint)(46 + filenameSize + extraSize + commentSize); | |||
| Encoding encoder = encodeUTF8 ? Encoding.UTF8 : DefaultEncoding; | |||
| ZipFileEntry zfe = new ZipFileEntry(); | |||
| zfe.Method = (Compression)method; | |||
| zfe.FilenameInZip = encoder.GetString(CentralDirImage, pointer + 46, filenameSize); | |||
| zfe.FileOffset = GetFileOffset(headerOffset); | |||
| zfe.FileSize = fileSize; | |||
| zfe.CompressedSize = comprSize; | |||
| zfe.HeaderOffset = headerOffset; | |||
| zfe.HeaderSize = headerSize; | |||
| zfe.Crc32 = crc32; | |||
| zfe.ModifyTime = DosTimeToDateTime(modifyTime); | |||
| if (commentSize > 0) | |||
| zfe.Comment = encoder.GetString(CentralDirImage, pointer + 46 + filenameSize + extraSize, commentSize); | |||
| result.Add(zfe); | |||
| pointer += (46 + filenameSize + extraSize + commentSize); | |||
| } | |||
| return result; | |||
| } | |||
| /// <summary> | |||
| /// Copy the contents of a stored file into a physical file | |||
| /// </summary> | |||
| /// <param name="_zfe">Entry information of file to extract</param> | |||
| /// <param name="_filename">Name of file to store uncompressed data</param> | |||
| /// <returns>True if success, false if not.</returns> | |||
| /// <remarks>Unique compression methods are Store and Deflate</remarks> | |||
| public bool ExtractFile(ZipFileEntry _zfe, string _filename) | |||
| { | |||
| // Make sure the parent directory exist | |||
| string path = System.IO.Path.GetDirectoryName(_filename); | |||
| if (!Directory.Exists(path)) | |||
| Directory.CreateDirectory(path); | |||
| // Check it is directory. If so, do nothing | |||
| if (Directory.Exists(_filename)) | |||
| return true; | |||
| Stream output = new FileStream(_filename, FileMode.Create, FileAccess.Write); | |||
| bool result = ExtractFile(_zfe, output); | |||
| if (result) | |||
| output.Close(); | |||
| File.SetCreationTime(_filename, _zfe.ModifyTime); | |||
| File.SetLastWriteTime(_filename, _zfe.ModifyTime); | |||
| return result; | |||
| } | |||
| /// <summary> | |||
| /// Copy the contents of a stored file into an opened stream | |||
| /// </summary> | |||
| /// <param name="_zfe">Entry information of file to extract</param> | |||
| /// <param name="_stream">Stream to store the uncompressed data</param> | |||
| /// <returns>True if success, false if not.</returns> | |||
| /// <remarks>Unique compression methods are Store and Deflate</remarks> | |||
| public bool ExtractFile(ZipFileEntry _zfe, Stream _stream) | |||
| { | |||
| if (!_stream.CanWrite) | |||
| throw new InvalidOperationException("Stream cannot be written"); | |||
| // check signature | |||
| byte[] signature = new byte[4]; | |||
| this.ZipFileStream.Seek(_zfe.HeaderOffset, SeekOrigin.Begin); | |||
| this.ZipFileStream.Read(signature, 0, 4); | |||
| if (BitConverter.ToUInt32(signature, 0) != 0x04034b50) | |||
| return false; | |||
| // Select input stream for inflating or just reading | |||
| Stream inStream; | |||
| if (_zfe.Method == Compression.Store) | |||
| inStream = this.ZipFileStream; | |||
| else if (_zfe.Method == Compression.Deflate) | |||
| inStream = new DeflateStream(this.ZipFileStream, CompressionMode.Decompress, true); | |||
| else | |||
| return false; | |||
| // Buffered copy | |||
| byte[] buffer = new byte[16384]; | |||
| this.ZipFileStream.Seek(_zfe.FileOffset, SeekOrigin.Begin); | |||
| uint bytesPending = _zfe.FileSize; | |||
| while (bytesPending > 0) | |||
| { | |||
| int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length)); | |||
| _stream.Write(buffer, 0, bytesRead); | |||
| bytesPending -= (uint)bytesRead; | |||
| } | |||
| _stream.Flush(); | |||
| if (_zfe.Method == Compression.Deflate) | |||
| inStream.Dispose(); | |||
| return true; | |||
| } | |||
| /// <summary> | |||
| /// Removes one of many files in storage. It creates a new Zip file. | |||
| /// </summary> | |||
| /// <param name="_zip">Reference to the current Zip object</param> | |||
| /// <param name="_zfes">List of Entries to remove from storage</param> | |||
| /// <returns>True if success, false if not</returns> | |||
| /// <remarks>This method only works for storage of type FileStream</remarks> | |||
| public static bool RemoveEntries(ref ZipStorer _zip, List<ZipFileEntry> _zfes) | |||
| { | |||
| if (!(_zip.ZipFileStream is FileStream)) | |||
| throw new InvalidOperationException("RemoveEntries is allowed just over streams of type FileStream"); | |||
| //Get full list of entries | |||
| List<ZipFileEntry> fullList = _zip.ReadCentralDir(); | |||
| //In order to delete we need to create a copy of the zip file excluding the selected items | |||
| string tempZipName = Path.GetTempFileName(); | |||
| string tempEntryName = Path.GetTempFileName(); | |||
| try | |||
| { | |||
| ZipStorer tempZip = ZipStorer.Create(tempZipName, string.Empty); | |||
| foreach (ZipFileEntry zfe in fullList) | |||
| { | |||
| if (!_zfes.Contains(zfe)) | |||
| { | |||
| if (_zip.ExtractFile(zfe, tempEntryName)) | |||
| { | |||
| tempZip.AddFile(zfe.Method, tempEntryName, zfe.FilenameInZip, zfe.Comment); | |||
| } | |||
| } | |||
| } | |||
| _zip.Close(); | |||
| tempZip.Close(); | |||
| File.Delete(_zip.FileName); | |||
| File.Move(tempZipName, _zip.FileName); | |||
| _zip = ZipStorer.Open(_zip.FileName, _zip.Access); | |||
| } | |||
| catch | |||
| { | |||
| return false; | |||
| } | |||
| finally | |||
| { | |||
| if (File.Exists(tempZipName)) | |||
| File.Delete(tempZipName); | |||
| if (File.Exists(tempEntryName)) | |||
| File.Delete(tempEntryName); | |||
| } | |||
| return true; | |||
| } | |||
| #endregion | |||
| #region Private methods | |||
| // Calculate the file offset by reading the corresponding local header | |||
| private uint GetFileOffset(uint _headerOffset) | |||
| { | |||
| byte[] buffer = new byte[2]; | |||
| this.ZipFileStream.Seek(_headerOffset + 26, SeekOrigin.Begin); | |||
| this.ZipFileStream.Read(buffer, 0, 2); | |||
| ushort filenameSize = BitConverter.ToUInt16(buffer, 0); | |||
| this.ZipFileStream.Read(buffer, 0, 2); | |||
| ushort extraSize = BitConverter.ToUInt16(buffer, 0); | |||
| return (uint)(30 + filenameSize + extraSize + _headerOffset); | |||
| } | |||
| /* Local file header: | |||
| local file header signature 4 bytes (0x04034b50) | |||
| version needed to extract 2 bytes | |||
| general purpose bit flag 2 bytes | |||
| compression method 2 bytes | |||
| last mod file time 2 bytes | |||
| last mod file date 2 bytes | |||
| crc-32 4 bytes | |||
| compressed size 4 bytes | |||
| uncompressed size 4 bytes | |||
| filename length 2 bytes | |||
| extra field length 2 bytes | |||
| filename (variable size) | |||
| extra field (variable size) | |||
| */ | |||
| private void WriteLocalHeader(ref ZipFileEntry _zfe) | |||
| { | |||
| long pos = this.ZipFileStream.Position; | |||
| Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding; | |||
| byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip); | |||
| this.ZipFileStream.Write(new byte[] { 80, 75, 3, 4, 20, 0 }, 0, 6); // No extra header | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time | |||
| this.ZipFileStream.Write(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 12); // unused CRC, un/compressed size, updated later | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // filename length | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length | |||
| this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length); | |||
| _zfe.HeaderSize = (uint)(this.ZipFileStream.Position - pos); | |||
| } | |||
| /* Central directory's File header: | |||
| central file header signature 4 bytes (0x02014b50) | |||
| version made by 2 bytes | |||
| version needed to extract 2 bytes | |||
| general purpose bit flag 2 bytes | |||
| compression method 2 bytes | |||
| last mod file time 2 bytes | |||
| last mod file date 2 bytes | |||
| crc-32 4 bytes | |||
| compressed size 4 bytes | |||
| uncompressed size 4 bytes | |||
| filename length 2 bytes | |||
| extra field length 2 bytes | |||
| file comment length 2 bytes | |||
| disk number start 2 bytes | |||
| internal file attributes 2 bytes | |||
| external file attributes 4 bytes | |||
| relative offset of local header 4 bytes | |||
| filename (variable size) | |||
| extra field (variable size) | |||
| file comment (variable size) | |||
| */ | |||
| private void WriteCentralDirRecord(ZipFileEntry _zfe) | |||
| { | |||
| Encoding encoder = _zfe.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding; | |||
| byte[] encodedFilename = encoder.GetBytes(_zfe.FilenameInZip); | |||
| byte[] encodedComment = encoder.GetBytes(_zfe.Comment); | |||
| this.ZipFileStream.Write(new byte[] { 80, 75, 1, 2, 23, 0xB, 20, 0 }, 0, 8); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)(_zfe.EncodeUTF8 ? 0x0800 : 0)), 0, 2); // filename and comment encoding | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(DateTimeToDosTime(_zfe.ModifyTime)), 0, 4); // zipping date and time | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // file CRC | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // compressed file size | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // uncompressed file size | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedFilename.Length), 0, 2); // Filename in zip | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // extra length | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // disk=0 | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // file type: binary | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0), 0, 2); // Internal file attributes | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)0x8100), 0, 2); // External file attributes (normal/readable) | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.HeaderOffset), 0, 4); // Offset of header | |||
| this.ZipFileStream.Write(encodedFilename, 0, encodedFilename.Length); | |||
| this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length); | |||
| } | |||
| /* End of central dir record: | |||
| end of central dir signature 4 bytes (0x06054b50) | |||
| number of this disk 2 bytes | |||
| number of the disk with the | |||
| start of the central directory 2 bytes | |||
| total number of entries in | |||
| the central dir on this disk 2 bytes | |||
| total number of entries in | |||
| the central dir 2 bytes | |||
| size of the central directory 4 bytes | |||
| offset of start of central | |||
| directory with respect to | |||
| the starting disk number 4 bytes | |||
| zipfile comment length 2 bytes | |||
| zipfile comment (variable size) | |||
| */ | |||
| private void WriteEndRecord(uint _size, uint _offset) | |||
| { | |||
| Encoding encoder = this.EncodeUTF8 ? Encoding.UTF8 : DefaultEncoding; | |||
| byte[] encodedComment = encoder.GetBytes(this.Comment); | |||
| this.ZipFileStream.Write(new byte[] { 80, 75, 5, 6, 0, 0, 0, 0 }, 0, 8); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count + ExistingFiles), 0, 2); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)Files.Count + ExistingFiles), 0, 2); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_size), 0, 4); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_offset), 0, 4); | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)encodedComment.Length), 0, 2); | |||
| this.ZipFileStream.Write(encodedComment, 0, encodedComment.Length); | |||
| } | |||
| // Copies all source file into storage file | |||
| private void Store(ref ZipFileEntry _zfe, Stream _source) | |||
| { | |||
| byte[] buffer = new byte[16384]; | |||
| int bytesRead; | |||
| uint totalRead = 0; | |||
| Stream outStream; | |||
| long posStart = this.ZipFileStream.Position; | |||
| long sourceStart = _source.Position; | |||
| if (_zfe.Method == Compression.Store) | |||
| outStream = this.ZipFileStream; | |||
| else | |||
| outStream = new DeflateStream(this.ZipFileStream, CompressionMode.Compress, true); | |||
| _zfe.Crc32 = 0 ^ 0xffffffff; | |||
| do | |||
| { | |||
| bytesRead = _source.Read(buffer, 0, buffer.Length); | |||
| totalRead += (uint)bytesRead; | |||
| if (bytesRead > 0) | |||
| { | |||
| outStream.Write(buffer, 0, bytesRead); | |||
| for (uint i = 0; i < bytesRead; i++) | |||
| { | |||
| _zfe.Crc32 = ZipStorer.CrcTable[(_zfe.Crc32 ^ buffer[i]) & 0xFF] ^ (_zfe.Crc32 >> 8); | |||
| } | |||
| } | |||
| } while (bytesRead == buffer.Length); | |||
| outStream.Flush(); | |||
| if (_zfe.Method == Compression.Deflate) | |||
| outStream.Dispose(); | |||
| _zfe.Crc32 ^= 0xffffffff; | |||
| _zfe.FileSize = totalRead; | |||
| _zfe.CompressedSize = (uint)(this.ZipFileStream.Position - posStart); | |||
| // Verify for real compression | |||
| if (_zfe.Method == Compression.Deflate && !this.ForceDeflating && _source.CanSeek && _zfe.CompressedSize > _zfe.FileSize) | |||
| { | |||
| // Start operation again with Store algorithm | |||
| _zfe.Method = Compression.Store; | |||
| this.ZipFileStream.Position = posStart; | |||
| this.ZipFileStream.SetLength(posStart); | |||
| _source.Position = sourceStart; | |||
| this.Store(ref _zfe, _source); | |||
| } | |||
| } | |||
| /* DOS Date and time: | |||
| MS-DOS date. The date is a packed value with the following format. Bits Description | |||
| 0-4 Day of the month (1–31) | |||
| 5-8 Month (1 = January, 2 = February, and so on) | |||
| 9-15 Year offset from 1980 (add 1980 to get actual year) | |||
| MS-DOS time. The time is a packed value with the following format. Bits Description | |||
| 0-4 Second divided by 2 | |||
| 5-10 Minute (0–59) | |||
| 11-15 Hour (0–23 on a 24-hour clock) | |||
| */ | |||
| private uint DateTimeToDosTime(DateTime _dt) | |||
| { | |||
| return (uint)( | |||
| (_dt.Second / 2) | (_dt.Minute << 5) | (_dt.Hour << 11) | | |||
| (_dt.Day << 16) | (_dt.Month << 21) | ((_dt.Year - 1980) << 25)); | |||
| } | |||
| private DateTime DosTimeToDateTime(uint _dt) | |||
| { | |||
| return new DateTime( | |||
| (int)(_dt >> 25) + 1980, | |||
| (int)(_dt >> 21) & 15, | |||
| (int)(_dt >> 16) & 31, | |||
| (int)(_dt >> 11) & 31, | |||
| (int)(_dt >> 5) & 63, | |||
| (int)(_dt & 31) * 2); | |||
| } | |||
| /* CRC32 algorithm | |||
| The 'magic number' for the CRC is 0xdebb20e3. | |||
| The proper CRC pre and post conditioning | |||
| is used, meaning that the CRC register is | |||
| pre-conditioned with all ones (a starting value | |||
| of 0xffffffff) and the value is post-conditioned by | |||
| taking the one's complement of the CRC residual. | |||
| If bit 3 of the general purpose flag is set, this | |||
| field is set to zero in the local header and the correct | |||
| value is put in the data descriptor and in the central | |||
| directory. | |||
| */ | |||
| private void UpdateCrcAndSizes(ref ZipFileEntry _zfe) | |||
| { | |||
| long lastPos = this.ZipFileStream.Position; // remember position | |||
| this.ZipFileStream.Position = _zfe.HeaderOffset + 8; | |||
| this.ZipFileStream.Write(BitConverter.GetBytes((ushort)_zfe.Method), 0, 2); // zipping method | |||
| this.ZipFileStream.Position = _zfe.HeaderOffset + 14; | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.Crc32), 0, 4); // Update CRC | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.CompressedSize), 0, 4); // Compressed size | |||
| this.ZipFileStream.Write(BitConverter.GetBytes(_zfe.FileSize), 0, 4); // Uncompressed size | |||
| this.ZipFileStream.Position = lastPos; // restore position | |||
| } | |||
| // Replaces backslashes with slashes to store in zip header | |||
| private string NormalizedFilename(string _filename) | |||
| { | |||
| string filename = _filename.Replace('\\', '/'); | |||
| int pos = filename.IndexOf(':'); | |||
| if (pos >= 0) | |||
| filename = filename.Remove(0, pos + 1); | |||
| return filename.Trim('/'); | |||
| } | |||
| // Reads the end-of-central-directory record | |||
| private bool ReadFileInfo() | |||
| { | |||
| if (this.ZipFileStream.Length < 22) | |||
| return false; | |||
| try | |||
| { | |||
| this.ZipFileStream.Seek(-17, SeekOrigin.End); | |||
| BinaryReader br = new BinaryReader(this.ZipFileStream); | |||
| do | |||
| { | |||
| this.ZipFileStream.Seek(-5, SeekOrigin.Current); | |||
| UInt32 sig = br.ReadUInt32(); | |||
| if (sig == 0x06054b50) | |||
| { | |||
| this.ZipFileStream.Seek(6, SeekOrigin.Current); | |||
| UInt16 entries = br.ReadUInt16(); | |||
| Int32 centralSize = br.ReadInt32(); | |||
| UInt32 centralDirOffset = br.ReadUInt32(); | |||
| UInt16 commentSize = br.ReadUInt16(); | |||
| // check if comment field is the very last data in file | |||
| if (this.ZipFileStream.Position + commentSize != this.ZipFileStream.Length) | |||
| return false; | |||
| // Copy entire central directory to a memory buffer | |||
| this.ExistingFiles = entries; | |||
| this.CentralDirImage = new byte[centralSize]; | |||
| this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin); | |||
| this.ZipFileStream.Read(this.CentralDirImage, 0, centralSize); | |||
| // Leave the pointer at the begining of central dir, to append new files | |||
| this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin); | |||
| return true; | |||
| } | |||
| } while (this.ZipFileStream.Position > 0); | |||
| } | |||
| catch { } | |||
| return false; | |||
| } | |||
| #endregion | |||
| #region IDisposable Members | |||
| /// <summary> | |||
| /// Closes the Zip file stream | |||
| /// </summary> | |||
| public void Dispose() | |||
| { | |||
| this.Close(); | |||
| } | |||
| #endregion | |||
| } | |||
| } | |||