Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
/*****************************************************************************
*
* File: opclib.cpp
*
* Description:
* This file contains definitions of utility functions that wrap some
* common operations needed when working with most Open Packaging Conventions
* (OPC) conformant files. The OPC specification is ECMA-376 Part 2.
*
* ------------------------------------
*
* This file is part of the Microsoft Windows SDK Code Samples.
*
* Copyright (C) Microsoft Corporation. All rights reserved.
*
* This source code is intended only as a supplement to Microsoft
* Development Tools and/or on-line documentation. See these other
* materials for detailed information regarding Microsoft code samples.
*
* THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
*
****************************************************************************/
#include "stdio.h"
#include "windows.h"
#include "shlobj.h"
#include "msopc.h"
#include "msxml6.h"
#include "opclib.h"
#include "util.h"
namespace opclib
{
// The definitive way to find a part of interest in a package, is to use a
// relationship type to find the relationship that targets the part and resolve
// the part name.
// The relationship type (core-properties relationship type) of the
// relationship targeting the Core Properties part, as defined by the OPC
// (ECMA-376 Part 2).
static const WCHAR g_corePropertiesRelationshipType[] =
L"https://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
// The expected content type of the Core Properties part, as defined by the OPC
// (ECMA-376 Part 2).
static const WCHAR g_corePropertiesContentType[] =
L"application/vnd.openxmlformats-package.core-properties+xml";
// Namespaces used in XPath selection queries for the Core Properties part.
// This includes a number of namespaces used in the Core Properties
// part as specified by the OPC.
static const WCHAR g_corePropertiesSelectionNamespaces[] =
L"xmlns:cp='https://schemas.openxmlformats.org/package/2006/metadata/core-properties' "
L"xmlns:dc='http://purl.org/dc/elements/1.1/' "
L"xmlns:dcterms='http://purl.org/dc/terms/' "
L"xmlns:dcmitype='http://purl.org/dc/dcmitype/' "
L"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'";
//////////////////////////////////////////////////////////////////////////////
// Description:
// Load a package file into a package object to be read.
//////////////////////////////////////////////////////////////////////////////
HRESULT
LoadPackage(
IOpcFactory *factory,
LPCWSTR packageName,
IOpcPackage **outPackage
)
{
IStream * sourceFileStream = NULL;
// Note: Do not use a writable stream to overwrite the data of a package
// that is read.
// Create a read-only stream over the package to prevent errors caused by
// simultaneously writing and reading from a package.
HRESULT hr = factory->CreateStreamOnFile(
packageName,
OPC_STREAM_IO_READ,
NULL,
0,
&sourceFileStream
);
if (SUCCEEDED(hr))
{
// Note: If a part is modified, it is accessed at least twice for
// reading and writing. Use the OPC_CACHE_ON_ACCESS flag to reduce
// overhead incurred by accessing a package component (in this case, a
// part) multiple times.
// Read the package into a package object.
// Note: A stream used to read a package is active for the lifetime of
// the package object into which it is read.
hr = factory->ReadPackageFromStream(
sourceFileStream,
OPC_CACHE_ON_ACCESS,
outPackage
);
}
// Release resources
if (sourceFileStream)
{
sourceFileStream->Release();
sourceFileStream = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// Save a package with the specified package file name.
//
// Note: Changes made to a package through a package object are not saved until
// the package is written.
//////////////////////////////////////////////////////////////////////////////
HRESULT
SavePackage(
IOpcFactory *factory,
IOpcPackage *package,
LPCWSTR targetFileName
)
{
IStream * targetFileStream = NULL;
// Note: Do not use a writable stream to overwrite the data of a package
// that is read.
// Create a writable stream over the specified target file name.
HRESULT hr = factory->CreateStreamOnFile(
targetFileName,
OPC_STREAM_IO_WRITE,
NULL,
0,
&targetFileStream
);
if (SUCCEEDED(hr))
{
// After a stream over the specified file is created successfully,
// write package data to the file.
hr = factory->WritePackageToStream(
package,
OPC_WRITE_DEFAULT,
targetFileStream
);
}
// Release resources
if (targetFileStream)
{
targetFileStream->Release();
targetFileStream = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// If the target of the relationship is a part, get the relative URI of the
// target and resolve this URI to the part name using the URI of the
// relationship's source as the base URI.
//////////////////////////////////////////////////////////////////////////////
HRESULT
ResolveTargetUriToPart(
IOpcRelationship *relationship,
IOpcPartUri **resolvedUri
)
{
IOpcUri * sourceUri = NULL;
IUri * targetUri = NULL;
OPC_URI_TARGET_MODE targetMode;
// Get the target mode of the relationship.
HRESULT hr = relationship->GetTargetMode(&targetMode);
if (SUCCEEDED(hr) && targetMode != OPC_URI_TARGET_MODE_INTERNAL)
{
// The target mode of the relationship was not internal.
// Function fails because the relationship does not target a part
// therefore, no part name can be resolved.
hr = E_FAIL;
}
// Get the segments of the URI and turn it into a valid part URI.
// The target should be resolved against the source URI of the
// relationship to expand relative URIs into part URIs with absolute
// paths.
if (SUCCEEDED(hr))
{
// Get a URI for the relationship's target. This URI might be
// relative to the source of the relationship.
hr = relationship->GetTargetUri(&targetUri);
}
if (SUCCEEDED(hr))
{
// Get the URI for the relationship's source.
hr = relationship->GetSourceUri(&sourceUri);
}
if (SUCCEEDED(hr))
{
// Form the API representation of the resultant part name.
hr = sourceUri->CombinePartUri(targetUri, resolvedUri);
}
// Release resources
if (sourceUri)
{
sourceUri->Release();
sourceUri = NULL;
}
if (targetUri)
{
targetUri->Release();
targetUri = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// Find a part that is the target of a package relationship based on the
// specified relationship type. If a content type is specified, check the
// content type of the part to ensure that only a part with the specified
// is retrieved.
//
// Note: This function finds the first, arbitrary part that is the target of
// a relationship of the specified type and has the specified content type, if
// a content type is provided.
//
// Example of the relationship markup for a relationship that targets a part:
// <Relationship Id="rId1"
// Type="https://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
// Target="word/document.xml" />
//
// The "Type" attribute (relationship type) is the definitive way to find a
// part of interest in a package. The relationship type is defined by the
// package designer or in the OPC and is therefore consistent and predictable.
//
// In contrast, the "Id" and "Target" attributes are arbitrary, and not recommended
// for finding specific parts of interest.
//
// Using a relationship type to find a part of interest requires a few steps.
// 1. Identify the relationship type, as defined by the package designer or the
// OPC, of the relationship(s) that has the part of interest as the target.
// 2. Get relationships of that relationship type from a relationship set.
// Note: It may be necessary to check that the number of retrieved
// relationships conforms to an expected number of relationships
// specified by the format designer or in the OPC.
// 3. If the target of a retrieved relationship is a part, resolve the part
// name.
// 4. Get the part.
// 5. If an expected content type is defined by the package designer or the OPC,
// ensure that the part has the correct content type.
// 6. If the found part has the expected content type, or if no expected
// content type is defined, return the part.
//////////////////////////////////////////////////////////////////////////////
HRESULT
FindPartByRelationshipType(
IOpcPackage *package,
LPCWSTR relationshipType, // Relationship type used to find the part.
LPCWSTR contentType, // Expected content type of part (optional).
IOpcPart **part
)
{
*part = NULL; // Enable checks against value of *part.
IOpcRelationshipSet * packageRels = NULL;
IOpcRelationshipEnumerator * packageRelsEnum = NULL;
IOpcPartSet * partSet = NULL;
BOOL hasNext = false;
HRESULT hr = package->GetPartSet(&partSet);
if (SUCCEEDED(hr))
{
// Get package relationships stored in the package's Relationships part.
hr = package->GetRelationshipSet(&packageRels);
}
if (SUCCEEDED(hr))
{
// Get package relationships of the specified relationship type.
hr = packageRels->GetEnumeratorForType(
relationshipType,
&packageRelsEnum
);
}
// Note: Though not performed by this sample, it may be necessary to check
// that the number of retrieved relationships conforms to the expected
// number of relationships specified by the format designer or in the OPC.
if (SUCCEEDED(hr))
{
// Move pointer to first package relationship.
hr = packageRelsEnum->MoveNext(&hasNext);
}
// Find the first, arbitrary part that is the target of a relationship
// of the specified type and has the specified content type, if a content
// type is provided. Abandon search when an error is encountered, when
// there are no more relationships in the enumerator, or when a part is
// found.
while (SUCCEEDED(hr) && hasNext && *part == NULL)
{
IOpcPartUri * partUri = NULL;
IOpcRelationship * currentRel = NULL;
BOOL partExists = FALSE;
hr = packageRelsEnum->GetCurrent(&currentRel);
if (SUCCEEDED(hr))
{
// There was a relationship of the specified type.
// Try to resolve the part name of the relationship's target.
hr = ResolveTargetUriToPart(currentRel, &partUri);
}
if (SUCCEEDED(hr))
{
// Part name resolved. Check that a part with that part name
// exists in the package.
hr = partSet->PartExists(partUri, &partExists);
}
if (SUCCEEDED(hr) && partExists)
{
// A part with the resolved part name exists in the package, so
// get a pointer to that part.
LPWSTR currentContentType = NULL;
IOpcPart * currentPart = NULL;
hr = partSet->GetPart(partUri, &currentPart);
if (SUCCEEDED(hr) && contentType != NULL)
{
// Content type specified.
// Get the content type of the part.
hr = currentPart->GetContentType(&currentContentType);
// Compare the content type of the part with the specified
// content type.
if (SUCCEEDED(hr) &&
0 == wcscmp(contentType, currentContentType))
{
// Part content type matches specified content type.
// Part found.
*part = currentPart;
currentPart = NULL;
}
}
if (SUCCEEDED(hr) && contentType == NULL)
{
// Content type not specified.
// Part found.
*part = currentPart;
currentPart = NULL;
}
// Release resources
CoTaskMemFree(static_cast<LPVOID>(currentContentType));
if (currentPart)
{
currentPart->Release();
currentPart = NULL;
}
}
// Get the next relationship of the specified type.
if (SUCCEEDED(hr))
{
hr = packageRelsEnum->MoveNext(&hasNext);
}
// Release resources
if (partUri)
{
partUri->Release();
partUri = NULL;
}
if (currentRel)
{
currentRel->Release();
currentRel = NULL;
}
}
if (SUCCEEDED(hr) && *part == NULL)
{
// Loop complete without errors and no part found.
hr = E_FAIL;
}
// Release resources
if (packageRels)
{
packageRels->Release();
packageRels = NULL;
}
if (packageRelsEnum)
{
packageRelsEnum->Release();
packageRelsEnum = NULL;
}
if (partSet)
{
partSet->Release();
partSet = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// Read part content into a new MSXML DOM document. If selection namespaces
// are provided, set up DOM document for XPath queries. Changes made to
// content from the DOM document must be written to the part explicitly.
//////////////////////////////////////////////////////////////////////////////
HRESULT
DOMFromPart(
IOpcPart * part,
LPCWSTR selectionNamespaces,
IXMLDOMDocument2 **document
)
{
IXMLDOMDocument2 * partContentXmlDocument = NULL;
IStream * partContentStream = NULL;
HRESULT hr = CoCreateInstance(
__uuidof(DOMDocument60),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IXMLDOMDocument2),
(LPVOID*)&partContentXmlDocument
);
// If selection namespaces were provided, configure the document
// for XPath queries.
if (SUCCEEDED(hr) && selectionNamespaces)
{
// Note: This sample uses a custom variant wrapper because
// ATL variants may throw.
AutoVariant v;
hr = v.SetBSTRValue(L"XPath");
if (SUCCEEDED(hr))
{
hr = partContentXmlDocument->setProperty(L"SelectionLanguage", v);
}
if (SUCCEEDED(hr))
{
AutoVariant v;
hr = v.SetBSTRValue(selectionNamespaces);
if (SUCCEEDED(hr))
{
hr = partContentXmlDocument->setProperty(L"SelectionNamespaces", v);
}
}
}
// Content stream from a part is read-write, though the DOM tree will only
// read from it.
if (SUCCEEDED(hr))
{
// Note: The DOM may hold the content stream open for the life of the
// DOM tree, and the content stream may hold a reference to the package.
hr = part->GetContentStream(&partContentStream);
}
if (SUCCEEDED(hr))
{
VARIANT_BOOL isSuccessful = VARIANT_FALSE;
AutoVariant vStream;
vStream.SetObjectValue(partContentStream);
hr = partContentXmlDocument->load(vStream, &isSuccessful);
if (SUCCEEDED(hr) && isSuccessful == VARIANT_FALSE)
{
// DOM load failed. This check intentionally simple.
hr = E_FAIL;
// For more about DOM load failure, see the MSXML documentation.
}
}
if (SUCCEEDED(hr))
{
// DOM loaded from part content
*document = partContentXmlDocument;
partContentXmlDocument = NULL;
}
// Release resources
if (partContentXmlDocument)
{
partContentXmlDocument->Release();
partContentXmlDocument = NULL;
}
if (partContentStream)
{
partContentStream->Release();
partContentStream = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// Helper function that finds the Core Properties part of a package.
//////////////////////////////////////////////////////////////////////////////
HRESULT
FindCorePropertiesPart(
IOpcPackage *package,
IOpcPart **part
)
{
return FindPartByRelationshipType(
package,
g_corePropertiesRelationshipType,
g_corePropertiesContentType,
part
);
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// Write core properties information to the console.
//////////////////////////////////////////////////////////////////////////////
HRESULT
PrintCoreProperties(
IOpcPackage *package
)
{
IOpcPart * corePropertiesPart = NULL;
IXMLDOMDocument2 * corePropertiesDom = NULL;
HRESULT hr = FindCorePropertiesPart(
package,
&corePropertiesPart
);
if (SUCCEEDED(hr))
{
hr = DOMFromPart(
corePropertiesPart,
g_corePropertiesSelectionNamespaces,
&corePropertiesDom
);
}
// Find & show author name.
if (SUCCEEDED(hr))
{
IXMLDOMNode * creatorNode = NULL;
BSTR text = NULL;
hr = corePropertiesDom->selectSingleNode(
L"//dc:creator",
&creatorNode
);
if (SUCCEEDED(hr) && creatorNode != NULL)
{
hr = creatorNode->get_text(&text);
}
if (SUCCEEDED(hr))
{
wprintf(L"Author: %s\n", (text != NULL) ? text : L"[missing]");
}
// Release resources
if (creatorNode)
{
creatorNode->Release();
creatorNode = NULL;
}
SysFreeString(text);
}
// Find & show properties indicating date of last modification and the
// name of who did it.
if (SUCCEEDED(hr))
{
IXMLDOMNode * modifiedName = NULL;
IXMLDOMNode * modifiedDate = NULL;
BSTR nameText = NULL;
BSTR dateText = NULL;
hr = corePropertiesDom->selectSingleNode(
L"//cp:lastModifiedBy",
&modifiedName
);
if (SUCCEEDED(hr) && modifiedName != NULL)
{
hr = modifiedName->get_text(&nameText);
}
if (SUCCEEDED(hr))
{
hr = corePropertiesDom->selectSingleNode(
L"//dcterms:modified",
&modifiedDate
);
}
if (SUCCEEDED(hr) && modifiedDate != NULL)
{
hr = modifiedDate->get_text(&dateText);
}
if (SUCCEEDED(hr))
{
wprintf(
L"Last modified by %s on %s\n",
(nameText != NULL) ? nameText : L"[missing]",
(dateText != NULL) ? dateText : L"[missing]"
);
}
// Release resources
if (modifiedName)
{
modifiedName->Release();
modifiedName = NULL;
}
if (modifiedDate)
{
modifiedDate->Release();
modifiedDate = NULL;
}
SysFreeString(nameText);
SysFreeString(dateText);
}
// Release resources
if (corePropertiesPart)
{
corePropertiesPart->Release();
corePropertiesPart = NULL;
}
if (corePropertiesDom)
{
corePropertiesDom->Release();
corePropertiesDom = NULL;
}
return hr;
}
//////////////////////////////////////////////////////////////////////////////
// Description:
// Set the modified by and author core properties of a specified package to a
// specified author.
//////////////////////////////////////////////////////////////////////////////
HRESULT
SetAuthorAndModifiedBy(
IOpcPackage *package,
LPCWSTR author
)
{
IOpcPart * corePropertiesPart = NULL;
IXMLDOMDocument2 * corePropertiesDom = NULL;
IXMLDOMNode * creatorNode = NULL;
IXMLDOMNode * modifiedName = NULL;
IStream * stream = NULL;
HRESULT hr = FindCorePropertiesPart(
package,
&corePropertiesPart
);
if (SUCCEEDED(hr))
{
hr = DOMFromPart(
corePropertiesPart,
g_corePropertiesSelectionNamespaces,
&corePropertiesDom
);
}
// Change text of the author and modified by properties to the specified
// author in the DOM.
if (SUCCEEDED(hr))
{
hr = corePropertiesDom->selectSingleNode(
L"//dc:creator",
&creatorNode
);
}
if (SUCCEEDED(hr) && creatorNode != NULL)
{
hr = creatorNode->put_text((BSTR)author);
}
if (SUCCEEDED(hr))
{
hr = corePropertiesDom->selectSingleNode(
L"//cp:lastModifiedBy",
&modifiedName
);
}
if (SUCCEEDED(hr) && modifiedName != NULL)
{
hr = modifiedName->put_text((BSTR)author);
}
// Note: Changes made using the DOM are not automatically applied to
// part content.
// Write the changes to part content of the Core Properties part.
if (SUCCEEDED(hr))
{
// Get the read/write part content stream
hr = corePropertiesPart->GetContentStream(&stream);
}
if (SUCCEEDED(hr))
{
// Clear old part content from the stream.
ULARGE_INTEGER zero = {0};
hr = stream->SetSize(zero);
}
if (SUCCEEDED(hr))
{
AutoVariant vStream;
vStream.SetObjectValue(stream);
// Write changes made to core properties data to the part content
// stream of the Core Properties part.
hr = corePropertiesDom->save(vStream);
}
// Release resources
if (corePropertiesPart)
{
corePropertiesPart->Release();
corePropertiesPart = NULL;
}
if (corePropertiesDom)
{
corePropertiesDom->Release();
corePropertiesDom = NULL;
}
if (creatorNode)
{
creatorNode->Release();
creatorNode = NULL;
}
if (modifiedName)
{
modifiedName->Release();
modifiedName = NULL;
}
if (stream)
{
stream->Release();
stream = NULL;
}
return hr;
}
} // namespace opclib
Related topics