summaryrefslogtreecommitdiff
path: root/samples/source
diff options
context:
space:
mode:
authorPriyanka-Gupta <gupta66priyanka@gmail.com>2020-01-29 10:33:16 +0530
committerPriyanka-Gupta <gupta66priyanka@gmail.com>2020-01-29 10:33:16 +0530
commit0872e35a30457d2ecf746a1bebdb7d94636d0e2f (patch)
treeb62c5636ca3955e9b2bb8631e7dc351e41d14912 /samples/source
parent8c4ca18c6dc28bf3774acb399bf6ee680985bf50 (diff)
XMP Toolkit SDK Jan 2020
Diffstat (limited to 'samples/source')
-rw-r--r--samples/source/CustomSchema.cpp244
-rw-r--r--samples/source/CustomSchemaNewDOM.cpp198
-rw-r--r--samples/source/DumpMainXMP.cpp135
-rw-r--r--samples/source/DumpScannedXMP.cpp171
-rw-r--r--samples/source/ModifyingXMP.cpp309
-rw-r--r--samples/source/ModifyingXMPNewDOM.cpp435
-rw-r--r--samples/source/ReadingXMP.cpp217
-rw-r--r--samples/source/ReadingXMPNewDOM.cpp286
-rw-r--r--samples/source/UnicodeCorrectness.cpp2815
-rw-r--r--samples/source/UnicodeParseSerialize.cpp512
-rw-r--r--samples/source/UnicodePerformance.cpp310
-rw-r--r--samples/source/XMPCoreCoverage.cpp1876
-rw-r--r--samples/source/XMPFilesCoverage.cpp343
-rw-r--r--samples/source/XMPIterations.cpp332
-rw-r--r--samples/source/common/DumpFile.cpp6255
-rw-r--r--samples/source/common/DumpFile.h25
-rw-r--r--samples/source/common/LargeFileAccess.cpp908
-rw-r--r--samples/source/common/LargeFileAccess.hpp291
-rw-r--r--samples/source/common/Log.cpp252
-rw-r--r--samples/source/common/Log.h97
-rw-r--r--samples/source/common/TagTree.cpp760
-rw-r--r--samples/source/common/TagTree.h203
-rw-r--r--samples/source/common/XMPScanner.cpp1439
-rw-r--r--samples/source/common/XMPScanner.hpp329
-rw-r--r--samples/source/common/globals.h99
-rw-r--r--samples/source/dumpfile/main.cpp241
-rw-r--r--samples/source/xmpcommand/Actions.cpp441
-rw-r--r--samples/source/xmpcommand/Actions.h50
-rw-r--r--samples/source/xmpcommand/PrintUsage.cpp84
-rw-r--r--samples/source/xmpcommand/PrintUsage.h19
-rw-r--r--samples/source/xmpcommand/XMPCommand.cpp218
31 files changed, 19894 insertions, 0 deletions
diff --git a/samples/source/CustomSchema.cpp b/samples/source/CustomSchema.cpp
new file mode 100644
index 0000000..9994cbd
--- /dev/null
+++ b/samples/source/CustomSchema.cpp
@@ -0,0 +1,244 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Tutorial solution for the Walkthrough 3 in the XMP Programmers Guide, Working with custom schema.
+*
+* Demonstrates how to work with a custom schema that has complex properties. It shows how to access
+* and modify properties with complex paths using the path composition utilities from the XMP API
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+#include <iostream>
+#include <fstream>
+
+// Made up namespace URI. Prefix will be xsdkEdit and xsdkUser
+const XMP_StringPtr kXMP_NS_SDK_EDIT = "http://ns.adobe/meta/sdk/Edit/";
+const XMP_StringPtr kXMP_NS_SDK_USERS = "http://ns.adobe/meta/sdk/User/";
+
+using namespace std;
+
+/**
+* Client defined callback function to dump XMP to a file. In this case an output file stream is used
+* to write a buffer, of length bufferSize, to a text file. This callback is called multiple
+* times during the DumpObject() operation. See the XMP API reference for details of
+* XMP_TextOutputProc() callbacks.
+*/
+XMP_Status XMPFileDump(void * refCon, XMP_StringPtr buffer, XMP_StringLen bufferSize)
+{
+ XMP_Status status = 0;
+ try
+ {
+ ofstream * outFile = static_cast<ofstream*>(refCon);
+ (*outFile).write(buffer, bufferSize);
+ }
+ catch(XMP_Error & e)
+ {
+ cout << e.GetErrMsg() << endl;
+ return -1;
+ }
+ return status;
+}
+
+/**
+* Client defined callback function to dump the registered namespaces to a file. In this case
+* an output file stream is used to write a buffer, of length bufferSize, to a text file. This
+* callback is called multiple times during the DumpObject() operation. See the XMP API
+* reference for details of XMP_TextOutputProc() callbacks.
+*/
+XMP_Status DumpNS(void * refCon, XMP_StringPtr buffer, XMP_StringLen bufferSize)
+{
+ XMP_Status status = 0;
+
+ try
+ {
+ ofstream *outFile= static_cast<ofstream*>(refCon);
+ (*outFile).write(buffer, bufferSize);
+ }
+ catch(XMP_Error & e)
+ {
+ cout << e.GetErrMsg() << endl;
+ return -1;
+ }
+ return status;
+}
+
+/**
+* Writes an XMP packet in XML format to a text file
+*
+* rdf - a pointer to the serialized XMP
+* filename - the name of the file to write to
+*/
+void writeRDFToFile(string * rdf, string filename)
+{
+ ofstream outFile;
+ outFile.open(filename.c_str(), ios::out);
+ outFile << *rdf;
+ outFile.close();
+}
+
+/**
+* Registers the namespaces that will be used with the custom schema. Then adds several new
+* properties to that schema. The properties are complex, containing nested arrays and structures.
+*
+* XMPFiles is not used in this sample, hence no external resource is updated with the metadata. The
+* created XMP object is serialized and written as RDF to a text file, the XMP object is dumped to
+* a text file and the registered namespaces are also dumped to a text file.*
+*
+*/
+int main()
+{
+ if(!SXMPMeta::Initialize())
+ {
+ cout << "Could not initialize Toolkit!";
+ }
+ else
+ {
+ try
+ {
+ // Register the namespaces
+ string actualPrefix;
+ SXMPMeta::RegisterNamespace(kXMP_NS_SDK_EDIT, "xsdkEdit", &actualPrefix);
+ SXMPMeta::RegisterNamespace(kXMP_NS_SDK_USERS, "xsdkUser",&actualPrefix);
+
+ SXMPMeta meta;
+
+ // Adds a user of the document
+ // 1. Add a new item onto the DocumentUsers array -
+ // 2. Compose a path to the last element of DocumentUsers array
+ // 3. Add a value for the User field of the UserDetails structure
+ // 4. Add a qualifier to the User field. Compose the path and set the value
+ // 5. Add a value for the DUID field of the UserDetails structure
+ // 6. Add a Contact property for the ContactDetails field of the UserDetails structure
+ // 7. Compose a path to the ContactDetails field of the UserDetails structure.
+ // 8. Create the fields of the ContactDetails structure and provide values
+
+ // Create/Append the top level DocumentUsers array. If the array exists a new item will be added
+ meta.AppendArrayItem(kXMP_NS_SDK_EDIT, "DocumentUsers", kXMP_PropValueIsArray, 0, kXMP_PropValueIsStruct);
+
+ // Compose a path to the last item in the DocumentUsers array, this will point to a UserDetails structure
+ string userItemPath;
+ SXMPUtils::ComposeArrayItemPath(kXMP_NS_SDK_EDIT, "DocumentUsers", kXMP_ArrayLastItem, &userItemPath);
+
+ // We now have a path to the structure, so we can set the field values
+ meta.SetStructField(kXMP_NS_SDK_EDIT, userItemPath.c_str(), kXMP_NS_SDK_USERS, "User", "John Smith", 0);
+
+ // Add a qualifier to the User field, first compose the path to the field and then add the qualifier
+ string userFieldPath;
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, userItemPath.c_str(), kXMP_NS_SDK_USERS, "User", &userFieldPath);
+ meta.SetQualifier(kXMP_NS_SDK_EDIT, userFieldPath.c_str(), kXMP_NS_SDK_USERS, "Role", "Dev Engineer");
+
+ // Compose a path to the DUID and set field value
+ string duidPath;
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, userItemPath.c_str(), kXMP_NS_SDK_USERS, "DUID", &duidPath);
+ meta.SetProperty_Int(kXMP_NS_SDK_EDIT, duidPath.c_str(), 2, 0);
+
+ // Add the ContactDetails field, this field is a Contact structure
+ meta.SetStructField(kXMP_NS_SDK_EDIT, userItemPath.c_str(), kXMP_NS_SDK_USERS, "ContactDetails", 0, kXMP_PropValueIsStruct);
+
+ // Compose a path to the field that has the ContactDetails structure
+ string contactStructPath;
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, userItemPath.c_str(), kXMP_NS_SDK_USERS, "ContactDetails", &contactStructPath);
+
+ // Now add the fields - all empty initially
+ meta.SetStructField(kXMP_NS_SDK_EDIT, contactStructPath.c_str(), kXMP_NS_SDK_USERS, "Email", 0, kXMP_PropArrayIsAlternate);
+ meta.SetStructField(kXMP_NS_SDK_EDIT, contactStructPath.c_str(), kXMP_NS_SDK_USERS, "Telephone", 0, kXMP_PropValueIsArray);
+ meta.SetStructField(kXMP_NS_SDK_EDIT, contactStructPath.c_str(), kXMP_NS_SDK_USERS, "BaseLocation", "", 0);
+
+ // Add some values for the fields
+ // Email: Get the path to the field named 'Email' in the ContactDetails structure and use it to append items
+ string path;
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, contactStructPath.c_str(), kXMP_NS_SDK_USERS, "Email", &path);
+ meta.AppendArrayItem(kXMP_NS_SDK_EDIT, path.c_str(), 0, "js@adobe.meta.com", 0);
+ meta.AppendArrayItem(kXMP_NS_SDK_EDIT, path.c_str(), 0, "js@adobe.home.com", 0);
+
+ // Telephone
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, contactStructPath.c_str(), kXMP_NS_SDK_USERS, "Telephone", &path);
+ meta.AppendArrayItem(kXMP_NS_SDK_EDIT, path.c_str(), 0, "89112", 0);
+ meta.AppendArrayItem(kXMP_NS_SDK_EDIT, path.c_str(), 0, "84432", 0);
+
+ // BaseLocation
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, contactStructPath.c_str(), kXMP_NS_SDK_USERS, "BaseLocation", &path);
+ meta.SetProperty(kXMP_NS_SDK_EDIT, path.c_str(), "London", 0);
+
+ // Add a user edit
+ // 1. Add an item (a structure) to the DocumentEdit array
+ // 2. Compose a path to the last item in the DocumentEdit array
+ // 3. Add fields and values to the EditDetails structure
+
+ // Create the array
+ meta.AppendArrayItem(kXMP_NS_SDK_EDIT, "DocumentEdit", kXMP_PropArrayIsOrdered, 0, kXMP_PropValueIsStruct);
+
+ // Compose a path to the last item of the DocumentEdit array, this gives the path to the structure
+ string lastItemPath;
+ SXMPUtils::ComposeArrayItemPath(kXMP_NS_SDK_EDIT, "DocumentEdit", kXMP_ArrayLastItem, &lastItemPath);
+
+ // Add the Date field
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, lastItemPath.c_str(), kXMP_NS_SDK_EDIT, "EditDate", &path);
+ XMP_DateTime dt;
+ SXMPUtils::CurrentDateTime(&dt);
+ meta.SetProperty_Date(kXMP_NS_SDK_EDIT, path.c_str(), dt, 0);
+
+ // Add the DUID field
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, lastItemPath.c_str(), kXMP_NS_SDK_EDIT, "DUID", &path);
+ meta.SetProperty_Int(kXMP_NS_SDK_EDIT, path.c_str(), 2, 0);
+
+ // Add the EditComments field
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_SDK_EDIT, lastItemPath.c_str(), kXMP_NS_SDK_EDIT, "EditComments", &path);
+ meta.SetLocalizedText(kXMP_NS_SDK_EDIT, path.c_str(), "en", "en-US", "Document created.", 0);
+
+ // Add the EditTool field
+ meta.SetStructField(kXMP_NS_SDK_EDIT, lastItemPath.c_str(), kXMP_NS_SDK_EDIT, "EditTool", "FrameXML", 0);
+
+ // Write the RDF to a file
+ cout << "writing RDF to file CS_RDF.txt" << endl;
+ string metaBuffer;
+ meta.SerializeToBuffer(&metaBuffer);
+ writeRDFToFile(&metaBuffer, "CS_RDF.txt");
+
+ // Dump the XMP object
+ cout << "dumping XMP object to file XMPDump.txt" << endl;
+ ofstream dumpFile;
+ dumpFile.open("XMPDump.txt", ios::out);
+ meta.DumpObject(XMPFileDump, &dumpFile);
+ dumpFile.close();
+
+ // Dump the namespaces to a file
+ cout << "dumping namespaces to file NameDump.txt" << endl;
+ dumpFile.open("NameDump.txt", ios::out);
+ meta.DumpNamespaces(XMPFileDump, &dumpFile);
+ dumpFile.close();
+
+ }
+ catch(XMP_Error & e)
+ {
+ cout << "ERROR: " << e.GetErrMsg();
+ }
+
+ SXMPMeta::Terminate();
+ }
+
+ return 0;
+}
diff --git a/samples/source/CustomSchemaNewDOM.cpp b/samples/source/CustomSchemaNewDOM.cpp
new file mode 100644
index 0000000..c1428ad
--- /dev/null
+++ b/samples/source/CustomSchemaNewDOM.cpp
@@ -0,0 +1,198 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Tutorial solution for the Walkthrough 3 in the XMP Programmers Guide, Working with custom schema.
+*
+* Demonstrates how to work with a custom schema that has complex properties. It shows how to access
+* and modify properties with complex paths using the path composition utilities from the XMP API
+*/
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Must be defined to give access to XMPFiles
+#define XMP_INCLUDE_XMPFILES 0
+
+#define ENABLE_NEW_DOM_MODEL 1
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+
+#include "XMPCore/Interfaces/IDOMImplementationRegistry.h"
+#include "XMPCore/Interfaces/IDOMParser.h"
+#include "XMPCore/Interfaces/IDOMSerializer.h"
+#include "XMPCore/Interfaces/IMetadata.h"
+#include "XMPCore/Interfaces/ICoreObjectFactory.h"
+#include "XMPCore/Interfaces/ISimpleNode.h"
+#include "XMPCore/Interfaces/IStructureNode.h"
+#include "XMPCore/Interfaces/IArrayNode.h"
+#include "XMPCore/Interfaces/INameSpacePrefixMap.h"
+//#include "XMPCore\Interfaces\IXMPCoreFwdDeclarations.h"
+#include "XMPCommon/Interfaces/IUTF8String.h"
+
+
+// Made up namespace URI. Prefix will be xsdkEdit and xsdkUser
+const XMP_StringPtr kXMP_NS_SDK_EDIT = "http://ns.adobe/meta/sdk/Edit/";
+const XMP_StringPtr kXMP_NS_SDK_USERS = "http://ns.adobe/meta/sdk/User/";
+
+using namespace std;
+
+/**
+* Writes an XMP packet in XML format to a text file
+*
+* rdf - a pointer to the serialized XMP
+* filename - the name of the file to write to
+*/
+void writeRDFToFile(string * rdf, string filename)
+{
+ ofstream outFile;
+ outFile.open(filename.c_str(), ios::out);
+ outFile << *rdf;
+ outFile.close();
+}
+
+/**
+* Registers the namespaces that will be used with the custom schema. Then adds several new
+* properties to that schema. The properties are complex, containing nested arrays and structures.
+*
+* XMPFiles is not used in this sample, hence no external resource is updated with the metadata. The
+* created XMP object is serialized and written as RDF to a text file, the XMP object is dumped to
+* a text file and the registered namespaces are also dumped to a text file.*
+*
+*/
+int main(int argc, const char * argv[])
+{
+
+ if (!SXMPMeta::Initialize())
+ {
+ cout << "Could not initialize toolkit!";
+ return -1;
+ }
+
+ else
+ {
+ try
+ {
+ /*
+ AdobeXMPCore::spINameSpacePrefixMap map = AdobeXMPCore::INameSpacePrefixMap::CreateNameSpacePrefixMap();
+ map->Insert("xsdkEdit", AdobeXMPCommon::npos, kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos);
+ map->Insert("xsdkUser", AdobeXMPCommon::npos, kXMP_NS_SDK_USERS, AdobeXMPCommon::npos);
+ */
+
+ SXMPMeta::RegisterNamespace(kXMP_NS_SDK_EDIT, "xsdkEdit", NULL);
+ SXMPMeta::RegisterNamespace(kXMP_NS_SDK_USERS,"xsdkUser", NULL);
+
+ // Adds a user of the document
+ // 1. Add a new item onto the DocumentUsers array -
+ // 2. Compose a path to the last element of DocumentUsers array
+ // 3. Add a value for the User field of the UserDetails structure
+ // 4. Add a qualifier to the User field. Compose the path and set the value
+ // 5. Add a value for the DUID field of the UserDetails structure
+ // 6. Add a Contact property for the ContactDetails field of the UserDetails structure
+ // 7. Compose a path to the ContactDetails field of the UserDetails structure.
+ // 8. Create the fields of the ContactDetails structure and provide values
+
+ // Create a top Level array node of DocumentUsers.
+ AdobeXMPCore::spIMetadata metaNode = AdobeXMPCore::IMetadata::CreateMetadata();
+ AdobeXMPCore::spIArrayNode DUarrayNode = AdobeXMPCore::IArrayNode::CreateUnorderedArrayNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "DocumentUsers", AdobeXMPCommon::npos);
+
+ // Append the UserDetails struct node as child of DocumentUsers array node
+ AdobeXMPCore::spIStructureNode DUstructNode1 = AdobeXMPCore::IStructureNode::CreateStructureNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "UserDetails", AdobeXMPCommon::npos);
+ DUarrayNode->AppendNode(DUstructNode1);
+
+ // Create simple nodes with property User and DUID as fields of UserDetails struct node
+ AdobeXMPCore::spINode UDsimpleNode1 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "User", AdobeXMPCommon::npos, "John Smith", AdobeXMPCommon::npos);
+ AdobeXMPCore::spINode UDsimpleNode2 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "DUID", AdobeXMPCommon::npos, "2", AdobeXMPCommon::npos);
+
+ // Create a structure ContactDetails as field of UserDetails structure
+ AdobeXMPCore::spIStructureNode UDstructNode3 = AdobeXMPCore::IStructureNode::CreateStructureNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "ContactDetails", AdobeXMPCommon::npos);
+
+ // Append all created fields as child of UserDetails struct node
+ DUstructNode1->AppendNode(UDsimpleNode1);
+ DUstructNode1->AppendNode(UDsimpleNode2);
+ DUstructNode1->AppendNode(UDstructNode3);
+
+ // Create an alternative Array E-mail as field of ContactDetails structure
+ AdobeXMPCore::spIArrayNode CDarrayNode1 = AdobeXMPCore::IArrayNode::CreateAlternativeArrayNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "E-Mail", AdobeXMPCommon::npos);
+
+ // Create an unordered Array Telephone as field of ContactDetails structure
+ AdobeXMPCore::spIArrayNode CDarrayNode2 = AdobeXMPCore::IArrayNode::CreateUnorderedArrayNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "Telephone", AdobeXMPCommon::npos);
+
+ // Create simple node with property BaseLocation
+ AdobeXMPCore::spINode CDsimpleNode3 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "BaseLocation", AdobeXMPCommon::npos, "London", AdobeXMPCommon::npos);
+
+ // Append all created fields as child of ContactDetails structure
+ UDstructNode3->AppendNode(CDarrayNode1);
+ UDstructNode3->AppendNode(CDarrayNode2);
+ UDstructNode3->AppendNode(CDsimpleNode3);
+
+ //Append items to E-Mail array
+ CDarrayNode1->AppendNode(AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "Mail1", AdobeXMPCommon::npos, "js@adobe.xmp.com", AdobeXMPCommon::npos));
+ CDarrayNode1->AppendNode(AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "Mail2", AdobeXMPCommon::npos, "js@adobe.home.com", AdobeXMPCommon::npos));
+
+ //Append items to Telephone array
+ CDarrayNode2->AppendNode(AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "Phone1", AdobeXMPCommon::npos, "89112", AdobeXMPCommon::npos));
+ CDarrayNode2->AppendNode(AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_USERS, AdobeXMPCommon::npos, "Phone2", AdobeXMPCommon::npos, "84432", AdobeXMPCommon::npos));
+
+ metaNode->AppendNode(DUarrayNode);
+
+ // Create unordered array DocumentEdit
+ AdobeXMPCore::spIArrayNode DEarrayNode = AdobeXMPCore::IArrayNode::CreateUnorderedArrayNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "DocumentEdit", AdobeXMPCommon::npos);
+
+ // Create structure EditDetails as arrayitem of DocumentEdit array
+ AdobeXMPCore::spIStructureNode DEstructNode1 = AdobeXMPCore::IStructureNode::CreateStructureNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "EditDetails", AdobeXMPCommon::npos);
+ DEarrayNode->AppendNode(DEstructNode1);
+
+ // Obtaining current date and time
+ XMP_DateTime dt;
+ SXMPUtils::CurrentDateTime(&dt);
+ string date;
+ SXMPUtils::ConvertFromDate(dt, &date);
+
+ // Creating simple node as field of EditDetails structure which will hold the current date
+ AdobeXMPCore::spINode EDsimpleNode1 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "EditDate", AdobeXMPCommon::npos, date.c_str(), AdobeXMPCommon::npos);
+
+ // Creating simple node Edittool as field of EditDetails structre
+ AdobeXMPCore::spINode EDsimpleNode2 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_SDK_EDIT, AdobeXMPCommon::npos, "EditTool", AdobeXMPCommon::npos, "FrameXML", AdobeXMPCommon::npos);
+
+ // Appending fields as child of EditDetails structure node
+ DEstructNode1->AppendNode(EDsimpleNode1);
+ DEstructNode1->AppendNode(EDsimpleNode2);
+ metaNode->AppendNode(DEarrayNode);
+
+ // Write the RDF to a file
+ cout << "writing RDF to file CS_RDF.txt" << endl;
+ AdobeXMPCore::spIDOMImplementationRegistry DOMRegistry = AdobeXMPCore::IDOMImplementationRegistry::GetDOMImplementationRegistry();
+ AdobeXMPCore::spIDOMSerializer serializer = DOMRegistry->GetSerializer("rdf");
+ std::string serializedPacket = serializer->Serialize(metaNode)->c_str();
+ writeRDFToFile(&serializedPacket, "CS_RDF.txt");
+
+ }
+ catch (XMP_Error & e)
+ {
+ cout << "ERROR: " << e.GetErrMsg();
+ }
+
+ }
+
+ return 0;
+
+}
diff --git a/samples/source/DumpMainXMP.cpp b/samples/source/DumpMainXMP.cpp
new file mode 100644
index 0000000..bf21274
--- /dev/null
+++ b/samples/source/DumpMainXMP.cpp
@@ -0,0 +1,135 @@
+// =================================================================================================
+// Copyright 2002 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Uses the XMPFiles component API to find the main XMP Packet for a data file, serializes the XMP, and writes
+* it to a human-readable log file. This is preferred over "dumb" packet scanning.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <ctime>
+
+#include <cstdlib>
+#include <stdexcept>
+#include <cerrno>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4127 ) // conditional expression is constant
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+#define TXMP_STRING_TYPE std::string
+#define XMP_INCLUDE_XMPFILES 1
+#include "public/include/XMP.hpp"
+#include "public/include/XMP.incl_cpp"
+
+using namespace std;
+
+static FILE * sLogFile = stdout;
+
+// =================================================================================================
+
+static void WriteMinorLabel ( FILE * log, const char * title )
+{
+
+ fprintf ( log, "\n// " );
+ for ( size_t i = 0; i < strlen(title); ++i ) fprintf ( log, "-" );
+ fprintf ( log, "--\n// %s :\n\n", title );
+ fflush ( log );
+
+} // WriteMinorLabel
+
+// =================================================================================================
+
+static XMP_Status DumpCallback ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ XMP_Status status = 0;
+ size_t count;
+ FILE * outFile = static_cast < FILE * > ( refCon );
+
+ count = fwrite ( outStr, 1, outLen, outFile );
+ if ( count != outLen ) status = errno;
+ return status;
+
+} // DumpCallback
+
+// =================================================================================================
+
+static void
+ProcessFile ( const char * fileName )
+{
+ bool ok;
+ char buffer [1000];
+
+ SXMPMeta xmpMeta;
+ SXMPFiles xmpFile;
+ XMP_FileFormat format;
+ XMP_OptionBits openFlags, handlerFlags;
+ XMP_PacketInfo xmpPacket;
+
+ sprintf ( buffer, "Dumping main XMP for %s", fileName );
+ WriteMinorLabel ( sLogFile, buffer );
+
+ xmpFile.OpenFile ( fileName, kXMP_UnknownFile, kXMPFiles_OpenForRead );
+ ok = xmpFile.GetFileInfo ( 0, &openFlags, &format, &handlerFlags );
+ if ( ! ok ) return;
+
+ fprintf ( sLogFile, "File info : format = %.8X, handler flags = %.8X\n", format, handlerFlags );
+ fflush ( sLogFile );
+
+ ok = xmpFile.GetXMP ( &xmpMeta, 0, &xmpPacket );
+ if ( ! ok ) return;
+
+ XMP_Int32 offset = (XMP_Int32)xmpPacket.offset;
+ XMP_Int32 length = xmpPacket.length;
+ fprintf ( sLogFile, "Packet info : offset = %d, length = %d\n", offset, length );
+ fflush ( sLogFile );
+
+ fprintf ( sLogFile, "\nInitial XMP from %s\n", fileName );
+ xmpMeta.DumpObject ( DumpCallback, sLogFile );
+
+ xmpFile.CloseFile();
+
+} // ProcessFile
+
+// =================================================================================================
+
+int
+main ( int argc, const char * argv [] )
+{
+
+ if ( ! SXMPMeta::Initialize() ) {
+ printf ( "## SXMPMeta::Initialize failed!\n" );
+ return -1;
+ }
+
+ XMP_OptionBits options = 0;
+ #if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+ #endif
+
+ if ( ! SXMPFiles::Initialize ( options ) ) {
+
+ printf ( "## SXMPFiles::Initialize failed!\n" );
+ return -1;
+ }
+
+ for ( int i = 1; i < argc; ++i ) ProcessFile ( argv[i] );
+
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ return 0;
+}
diff --git a/samples/source/DumpScannedXMP.cpp b/samples/source/DumpScannedXMP.cpp
new file mode 100644
index 0000000..9d7e65e
--- /dev/null
+++ b/samples/source/DumpScannedXMP.cpp
@@ -0,0 +1,171 @@
+// =================================================================================================
+// Copyright 2002 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Scans a data file to find all embedded XMP Packets, without using the smart handlers. If a packet is found,
+* serializes the XMP and writes it to log file.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <ctime>
+
+#include <cstdlib>
+#include <stdexcept>
+#include <cerrno>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4127 ) // conditional expression is constant
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+#define TXMP_STRING_TYPE std::string
+
+#include "public/include/XMP.hpp"
+#include "public/include/XMP.incl_cpp"
+
+
+#include "XMPFiles/source/FormatSupport/XMPScanner.hpp"
+#include "XMPFiles/source/FormatSupport/XMPScanner.cpp"
+
+
+using namespace std;
+
+// =================================================================================================
+
+static XMP_Status DumpCallback ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ XMP_Status status = 0;
+ size_t count;
+ FILE * outFile = static_cast < FILE * > ( refCon );
+
+ count = fwrite ( outStr, 1, outLen, outFile );
+ if ( count != outLen ) status = errno;
+ return status;
+
+} // DumpCallback
+
+// =================================================================================================
+
+static void
+ProcessPacket ( const char * fileName,
+ FILE * inFile,
+ size_t offset,
+ size_t length )
+{
+ std::string xmlString;
+ xmlString.append ( length, ' ' );
+ fseek ( inFile, (long)offset, SEEK_SET );
+ fread ( (void*)xmlString.data(), 1, length, inFile );
+
+ char title [1000];
+
+ sprintf ( title, "// Dumping raw input for \"%s\" (%zu..%zu)", fileName, offset, (offset + length - 1) );
+ printf ( "// " );
+ for ( size_t i = 3; i < strlen(title); ++i ) printf ( "=" );
+ printf ( "\n\n%s\n\n%.*s\n\n", title, (int)length, xmlString.c_str() );
+ fflush ( stdout );
+
+ SXMPMeta xmpObj;
+ try {
+ xmpObj.ParseFromBuffer ( xmlString.c_str(), (XMP_StringLen)length );
+ } catch ( ... ) {
+ printf ( "## Parse failed\n\n" );
+ return;
+ }
+
+ xmpObj.DumpObject ( DumpCallback, stdout );
+ fflush ( stdout );
+
+ string xmpString;
+ xmpObj.SerializeToBuffer ( &xmpString, kXMP_OmitPacketWrapper );
+ printf ( "\nPretty serialization, %zu bytes :\n\n%s\n", xmpString.size(), xmpString.c_str() );
+ fflush ( stdout );
+
+ xmpObj.SerializeToBuffer ( &xmpString, (kXMP_OmitPacketWrapper | kXMP_UseCompactFormat) );
+ printf ( "Compact serialization, %zu bytes :\n\n%s\n", xmpString.size(), xmpString.c_str() );
+ fflush ( stdout );
+
+} // ProcessPacket
+
+// =================================================================================================
+
+static void
+ProcessFile ( const char * fileName )
+{
+ FILE * inFile;
+ size_t fileLen, readCount;
+ size_t snipCount;
+ char buffer [64*1024];
+
+ // ---------------------------------------------------------------------
+ // Use the scanner to find all of the packets then process each of them.
+
+ inFile = fopen ( fileName, "rb" );
+ if ( inFile == 0 ) {
+ printf ( "Can't open \"%s\"\n", fileName );
+ return;
+ }
+
+ fseek ( inFile, 0, SEEK_END );
+ fileLen = ftell ( inFile ); // ! Only handles up to 2GB files.
+ fseek ( inFile, 0, SEEK_SET );
+
+ XMPScanner scanner ( fileLen );
+
+ for ( size_t filePos = 0; true; filePos += readCount ) {
+ readCount = fread ( buffer, 1, sizeof(buffer), inFile );
+ if ( readCount == 0 ) break;
+ scanner.Scan ( buffer, filePos, readCount );
+ }
+
+ snipCount = scanner.GetSnipCount();
+
+ XMPScanner::SnipInfoVector snips (snipCount);
+ scanner.Report ( snips );
+
+ size_t packetCount = 0;
+ for ( size_t s = 0; s < snipCount; ++s ) {
+ if ( snips[s].fState == XMPScanner::eValidPacketSnip ) {
+ ++packetCount;
+ ProcessPacket ( fileName, inFile, (size_t)snips[s].fOffset, (size_t)snips[s].fLength );
+ }
+ }
+ if ( packetCount == 0 ) printf ( " No packets found\n" );
+
+} // ProcessFile
+
+// =================================================================================================
+
+extern "C" int
+main ( int argc, const char * argv [] )
+{
+
+ if ( ! SXMPMeta::Initialize() ) {
+ printf ( "## SXMPMeta::Initialize failed!\n" );
+ return -1;
+ }
+
+ if ( argc != 2 ) // 2 := command and 1 parameter
+ {
+ printf ("usage: DumpScannedXMP (filename)\n");
+ return 0;
+ }
+
+ for ( int i = 1; i < argc; ++i ) ProcessFile ( argv[i] );
+
+ SXMPMeta::Terminate();
+ return 0;
+
+}
diff --git a/samples/source/ModifyingXMP.cpp b/samples/source/ModifyingXMP.cpp
new file mode 100644
index 0000000..df5c86f
--- /dev/null
+++ b/samples/source/ModifyingXMP.cpp
@@ -0,0 +1,309 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Tutorial solution for Walkthrough 2 in the XMP Programmers Guide, Modifying XMP
+* Demonstrates how to open a file for update, and modifying the contained XMP before writing it back to the file.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Must be defined to give access to XMPFiles
+#define XMP_INCLUDE_XMPFILES 1
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+/**
+* Display some property values to the console
+*
+* meta - a pointer to the XMP object that will have the properties read
+*/
+void displayPropertyValues(SXMPMeta * meta)
+{
+ // Read a simple property
+ string simpleValue; //Stores the value for the property
+ meta->GetProperty(kXMP_NS_XMP, "CreatorTool", &simpleValue, 0);
+ cout << "meta:CreatorTool = " << simpleValue << endl;
+
+ // Get the first and second element in the dc:creator array
+ string elementValue;
+ meta->GetArrayItem(kXMP_NS_DC, "creator", 1, &elementValue, 0);
+ if(elementValue != "")
+ {
+ cout << "dc:creator[1] = " << elementValue << endl;
+ meta->GetArrayItem(kXMP_NS_DC, "creator", 2, &elementValue, 0);
+ cout << "dc:creator[2] = " << elementValue << endl;
+ }
+
+ // Get the the entire dc:subject array
+ string propValue;
+ int arrSize = meta->CountArrayItems(kXMP_NS_DC, "subject");
+ for(int i = 1; i <= arrSize;i++)
+ {
+ meta->GetArrayItem(kXMP_NS_DC, "subject", i, &propValue, 0);
+ cout << "dc:subject[" << i << "] = " << propValue << endl;
+ }
+
+ // Get the dc:title for English and French
+ string itemValue;
+ string actualLang;
+ meta->GetLocalizedText(kXMP_NS_DC, "title", "en", "en-US", 0, &itemValue, 0);
+ cout << "dc:title in English = " << itemValue << endl;
+
+ meta->GetLocalizedText(kXMP_NS_DC, "title", "fr", "fr-FR", 0, &itemValue, 0);
+ cout << "dc:title in French = " << itemValue << endl;
+
+ // Get dc:MetadataDate
+ XMP_DateTime myDate;
+ if(meta->GetProperty_Date(kXMP_NS_XMP, "MetadataDate", &myDate, 0))
+ {
+ // Convert the date struct into a convenient string and display it
+ string myDateStr;
+ SXMPUtils::ConvertFromDate(myDate, &myDateStr);
+ cout << "meta:MetadataDate = " << myDateStr << endl;
+ }
+
+ cout << "----------------------------------------" << endl;
+}
+
+/**
+* Creates an XMP object from an RDF string. The string is used to
+* to simulate creating and XMP object from multiple input buffers.
+* The last call to ParseFromBuffer has no kXMP_ParseMoreBuffers options,
+* thereby indicating this is the last input buffer.
+*/
+SXMPMeta createXMPFromRDF()
+{
+ const char * rdf =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ "<rdf:Description rdf:about='' xmlns:dc='http://purl.org/dc/elements/1.1/'>"
+ "<dc:subject>"
+ "<rdf:Bag>"
+ "<rdf:li>XMP</rdf:li>"
+ "<rdf:li>SDK</rdf:li>"
+ "<rdf:li>Sample</rdf:li>"
+ "</rdf:Bag>"
+ "</dc:subject>"
+ "<dc:format>image/tiff</dc:format>"
+ "</rdf:Description>"
+ "</rdf:RDF>";
+
+ SXMPMeta meta;
+ // Loop over the rdf string and create the XMP object
+ // 10 characters at a time
+ int i;
+ for (i = 0; i < (long)strlen(rdf) - 10; i += 10 )
+ {
+ meta.ParseFromBuffer ( &rdf[i], 10, kXMP_ParseMoreBuffers );
+ }
+
+ // The last call has no kXMP_ParseMoreBuffers options, signifying
+ // this is the last input buffer
+ meta.ParseFromBuffer ( &rdf[i], (XMP_StringLen) strlen(rdf) - i );
+ return meta;
+
+}
+
+/**
+* Writes an XMP packet in XML format to a text file
+*
+* rdf - a pointer to the serialized XMP
+* filename - the name of the file to write to
+*/
+void writeRDFToFile(string * rdf, string filename)
+{
+ ofstream outFile;
+ outFile.open(filename.c_str(), ios::out);
+ outFile << *rdf;
+ outFile.close();
+}
+
+/**
+* Initializes the toolkit and attempts to open a file for updating its metadata. Initially
+* an attempt to open the file is done with a handler, if this fails then the file is opened with
+* packet scanning. Once the file is open several properties are read and displayed in the console.
+*
+* Several properties are then modified, first by checking for their existence and then, if they
+* exist, by updating their values. The updated properties are then displayed again in the console.
+*
+* Next a new XMP object is created from an RDF stream, the properties from the new XMP object are
+* appended to the original XMP object and the updated properties are displayed in the console for
+* last time.
+*
+* The updated XMP object is then serialized in different formats and written to text files. Lastly,
+* the modified XMP is written back to the resource file.
+*/
+int main ( int argc, const char * argv[] )
+{
+ if ( argc != 2 ) // 2 := command and 1 parameter
+ {
+ cout << "usage: ModifyingXMP (filename)" << endl;
+ return 0;
+ }
+
+ string filename = string( argv[1] );
+
+ if(!SXMPMeta::Initialize())
+ {
+ cout << "Could not initialize toolkit!";
+ return -1;
+ }
+
+ XMP_OptionBits options = 0;
+ #if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+ #endif
+
+ // Must initialize SXMPFiles before we use it
+ if(SXMPFiles::Initialize(options))
+ {
+ try
+ {
+ // Options to open the file with - open for editing and use a smart handler
+ XMP_OptionBits opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUseSmartHandler;
+
+ bool ok;
+ SXMPFiles myFile;
+ std::string status = "";
+
+ // First we try and open the file
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ if( ! ok )
+ {
+ status += "No smart handler available for " + filename + "\n";
+ status += "Trying packet scanning.\n";
+
+ // Now try using packet scanning
+ opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning;
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ }
+
+ // If the file is open then read get the XMP data
+ if(ok)
+ {
+ cout << status << endl;
+ cout << filename << " is opened successfully" << endl;
+ // Create the XMP object and get the XMP data
+ SXMPMeta meta;
+ myFile.GetXMP(&meta);
+
+ // Display some properties in the console
+ displayPropertyValues(&meta);
+
+ ///////////////////////////////////////////////////
+ // Now modify the XMP
+ if(meta.DoesPropertyExist(kXMP_NS_XMP, "CreatorTool"))
+ {
+ // Update xap:CreatorTool - we don't need to set any option bits
+ meta.SetProperty(kXMP_NS_XMP, "CreatorTool", "Updated By XMP SDK", 0);
+ }
+
+ // Update the Metadata Date
+ XMP_DateTime updatedTime;
+ // Get the current time. This is a UTC time automatically
+ // adjusted for the local time
+ SXMPUtils::CurrentDateTime(&updatedTime);
+ if(meta.DoesPropertyExist(kXMP_NS_XMP, "MetadataDate"))
+ {
+ meta.SetProperty_Date(kXMP_NS_XMP, "MetadataDate", updatedTime, 0);
+ }
+
+ // Add an item onto the dc:creator array
+ // Note the options used, kXMP_PropArrayIsOrdered, if the array does not exist it will be created
+ meta.AppendArrayItem(kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, "Author Name", 0);
+ meta.AppendArrayItem(kXMP_NS_DC, "creator", kXMP_PropArrayIsOrdered, "Another Author Name", 0);
+
+ // Now update alt-text properties
+ meta.SetLocalizedText(kXMP_NS_DC, "title", "en", "en-US", "An English title");
+ meta.SetLocalizedText(kXMP_NS_DC, "title", "fr", "fr-FR", "Un titre Francais");
+
+ // Display the properties again to show changes
+ cout << "After update:" << endl;
+ displayPropertyValues(&meta);
+
+ // Create a new XMP object from an RDF string
+ SXMPMeta rdfMeta = createXMPFromRDF();
+
+ // Append the newly created properties onto the original XMP object
+ // This will:
+ // a) Add ANY new TOP LEVEL properties in the source (rdfMeta) to the destination (meta)
+ // b) Replace any top level properties in the source with the matching properties from the destination
+ SXMPUtils::ApplyTemplate(&meta, rdfMeta, kXMPTemplate_AddNewProperties | kXMPTemplate_ReplaceExistingProperties | kXMPTemplate_IncludeInternalProperties);
+
+ // Display the properties again to show changes
+ cout << "After Appending Properties:" << endl;
+ displayPropertyValues(&meta);
+
+ // Serialize the packet and write the buffer to a file
+ // Let the padding be computed and use the default linefeed and indents without limits
+ string metaBuffer;
+ meta.SerializeToBuffer(&metaBuffer, 0, 0, "", "", 0);
+
+ // Write the packet to a file as RDF
+ writeRDFToFile(&metaBuffer, filename+"_XMP_RDF.txt");
+
+ // Write the packet to a file but this time as compact RDF
+ XMP_OptionBits outOpts = kXMP_OmitPacketWrapper | kXMP_UseCompactFormat;
+ meta.SerializeToBuffer(&metaBuffer, outOpts);
+ writeRDFToFile(&metaBuffer, filename+"_XMP_RDF_Compact.txt");
+
+ // Check we can put the XMP packet back into the file
+ if(myFile.CanPutXMP(meta))
+ {
+ // If so then update the file with the modified XMP
+ myFile.PutXMP(meta);
+ }
+
+ // Close the SXMPFile. This *must* be called. The XMP is not
+ // actually written and the disk file is not closed until this call is made.
+ myFile.CloseFile();
+ }
+ else
+ {
+ cout << "Unable to open " << filename << endl;
+ }
+ }
+ catch(XMP_Error & e)
+ {
+ cout << "ERROR: " << e.GetErrMsg() << endl;
+ }
+
+ // Terminate the toolkit
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ }
+ else
+ {
+ cout << "Could not initialize SXMPFiles.";
+ return -1;
+ }
+
+ return 0;
+}
+
+
diff --git a/samples/source/ModifyingXMPNewDOM.cpp b/samples/source/ModifyingXMPNewDOM.cpp
new file mode 100644
index 0000000..73f5fb3
--- /dev/null
+++ b/samples/source/ModifyingXMPNewDOM.cpp
@@ -0,0 +1,435 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Tutorial solution for Walkthrough 2 in the XMP Programmers Guide, Modifying XMP
+* Demonstrates how to open a file for update, and modifying the contained XMP before writing it back to the file.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Must be defined to give access to XMPFiles
+#define XMP_INCLUDE_XMPFILES 1
+
+#define ENABLE_NEW_DOM_MODEL 1
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+#include "XMPCore/Interfaces/IDOMImplementationRegistry.h"
+#include "XMPCore/Interfaces/IDOMParser.h"
+#include "XMPCore/Interfaces/IDOMSerializer.h"
+#include "XMPCore/Interfaces/IMetadata.h"
+#include "XMPCore/Interfaces/ICoreObjectFactory.h"
+#include "XMPCore/Interfaces/ISimpleNode.h"
+#include "XMPCore/Interfaces/IStructureNode.h"
+#include "XMPCore/Interfaces/IArrayNode.h"
+#include "XMPCore/Interfaces/INameSpacePrefixMap.h"
+#include "XMPCommon/Interfaces/IUTF8String.h"
+#include "XMPCore/Interfaces/INodeIterator.h"
+
+
+using namespace std;
+using namespace AdobeXMPCore;
+
+void GetLocalizedText(spIArrayNode titleNode, const char* specificLang, const char* genericLang, string lang)
+{
+ AdobeXMPCore::spINode currItem;
+ const size_t itemLim = titleNode->ChildCount();
+ size_t itemNum;
+
+ spISimpleNode xmlLangQualifierNode, currItemNode;
+ for (itemNum = 1; itemNum <= itemLim; ++itemNum)
+ {
+ currItem = titleNode->GetNodeAtIndex(itemNum);
+ if (currItem != NULL)
+ {
+ xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode();
+ if (!strcmp(xmlLangQualifierNode->GetValue()->c_str(), specificLang)) {
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" << " " << lang << " " << currItemNode->GetValue()->c_str() << endl;
+ return;
+ }
+ }
+ }
+
+ if (*genericLang != 0)
+ {
+ // Look for the first partial match with the generic language.
+ const size_t genericLen = strlen(genericLang);
+ for (itemNum = 1; itemNum <= itemLim; ++itemNum) {
+ currItem = titleNode->GetNodeAtIndex(itemNum);
+ xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode();
+ XMP_StringPtr currLang = xmlLangQualifierNode->GetValue()->c_str();
+ const size_t currLangSize = xmlLangQualifierNode->GetValue()->size();
+ if ((currLangSize >= genericLen) &&
+ !strncmp(currLang, genericLang, genericLen) &&
+ ((currLangSize == genericLen) || (currLang[genericLen] == '-')))
+ {
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" << " " << lang << " " << currItemNode->GetValue()->c_str() << endl;
+ return;
+ }
+ }
+ }
+
+ // Look for an 'x-default' item.
+ for (itemNum = 1; itemNum <= itemLim; ++itemNum) {
+ currItem = titleNode->GetNodeAtIndex(itemNum);
+ xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode();
+ if (!strcmp(xmlLangQualifierNode->GetValue()->c_str(), "x-default")) {
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" << " " << lang << " " << currItemNode->GetValue()->c_str() << endl;
+ return;
+ }
+ }
+
+ // Everything failed, choose the first item.
+ currItem = titleNode->GetNodeAtIndex(1);
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" << " " << lang << " " << currItemNode->GetValue() << endl;
+ return;
+
+}
+
+
+
+/**
+* Display some property values to the console
+*
+* meta - a pointer to the XMP object that will have the properties read
+*/
+void displayPropertyValues(AdobeXMPCore::spIMetadata metaNode)
+{
+ // Read a simple property
+ AdobeXMPCore::spISimpleNode simpleNode = metaNode->GetSimpleNode(kXMP_NS_XMP, AdobeXMPCommon::npos, "CreatorTool", AdobeXMPCommon::npos);
+ if (simpleNode != NULL)
+ {
+ string simpleNodeValue = simpleNode->GetValue()->c_str();
+ cout << "CreatorTool = " << simpleNodeValue << endl;
+ }
+
+ // Get the first element in the dc:creator array
+ AdobeXMPCore::spIArrayNode arrayNode = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "creator", AdobeXMPCommon::npos);
+ if (arrayNode != NULL)
+ {
+ AdobeXMPCore::spISimpleNode arrayNodeChild = arrayNode->GetSimpleNodeAtIndex(1);
+ if (arrayNodeChild != NULL)
+ {
+ string arrayNodeChildValue = arrayNodeChild->GetValue()->c_str();
+ cout << "dc:creator[1] = " << arrayNodeChildValue << endl;
+ }
+
+ AdobeXMPCore::spISimpleNode arrayNodeChild1 = arrayNode->GetSimpleNodeAtIndex(2);
+ if (arrayNodeChild1 != NULL)
+ {
+ string arrayNodeChildValue1 = arrayNodeChild1->GetValue()->c_str();
+ cout << "dc:creator[2] = " << arrayNodeChildValue1 << endl;
+ }
+ }
+
+ // Get the the entire dc:subject array
+ AdobeXMPCore::spIArrayNode subjectArray = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "subject", AdobeXMPCommon::npos);
+ if (subjectArray != NULL)
+ {
+ sizet arraySize = subjectArray->ChildCount();
+ for (sizet i = 1; i <= arraySize; i++)
+ {
+ AdobeXMPCore::spISimpleNode subjectChild = subjectArray->GetSimpleNodeAtIndex(i);
+ if (subjectChild != NULL)
+ {
+ string propValue = subjectChild->GetValue()->c_str();
+ cout << "dc:subject[" << i << "] = " << propValue << endl;
+ }
+ }
+ }
+ // Get the dc:title for English and French
+ AdobeXMPCore::spIArrayNode titleNode = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "title", AdobeXMPCommon::npos);
+ if (titleNode != NULL)
+ {
+ GetLocalizedText(titleNode, "en-US", "en", "English");
+ GetLocalizedText(titleNode, "fr-FR", "fr", "French");
+ }
+
+ // Get dc:MetadataDate
+ AdobeXMPCore::spISimpleNode dateNode = metaNode->GetSimpleNode(kXMP_NS_XMP, AdobeXMPCommon::npos, "MetadataDate", AdobeXMPCommon::npos);
+ if(dateNode != NULL)
+ {
+ string date = dateNode->GetValue()->c_str();
+ cout << "meta:MetadataDate = " << date << endl;
+ }
+
+
+ // See if the flash struct exists and see if it was used
+ AdobeXMPCore::spIStructureNode flashNode = metaNode->GetStructureNode(kXMP_NS_EXIF, AdobeXMPCommon::npos, "Flash", AdobeXMPCommon::npos);
+ if (flashNode != NULL)
+ {
+ AdobeXMPCore::spISimpleNode field = flashNode->GetSimpleNode(kXMP_NS_EXIF, AdobeXMPCommon::npos, "Fired", AdobeXMPCommon::npos);
+ if (field != NULL)
+ {
+ string fieldValue = field->GetValue()->c_str();
+ cout << "Flash Used = " << fieldValue << endl;
+ }
+ }
+
+
+ cout << "----------------------------------------" << endl;
+}
+
+/**
+* Creates an XMP object from an RDF string. The string is used to
+* to simulate creating and XMP object from multiple input buffers.
+* The last call to ParseFromBuffer has no kXMP_ParseMoreBuffers options,
+* thereby indicating this is the last input buffer.
+*/
+SXMPMeta createXMPFromRDF()
+{
+ const char * rdf =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ "<rdf:Description rdf:about='' xmlns:dc='http://purl.org/dc/elements/1.1/'>"
+ "<dc:subject>"
+ "<rdf:Bag>"
+ "<rdf:li>XMP</rdf:li>"
+ "<rdf:li>SDK</rdf:li>"
+ "<rdf:li>Sample</rdf:li>"
+ "</rdf:Bag>"
+ "</dc:subject>"
+ "<dc:format>image/tiff</dc:format>"
+ "</rdf:Description>"
+ "</rdf:RDF>";
+
+ SXMPMeta meta;
+ // Loop over the rdf string and create the XMP object
+ // 10 characters at a time
+ int i;
+ for (i = 0; i < (long)strlen(rdf) - 10; i += 10)
+ {
+ meta.ParseFromBuffer(&rdf[i], 10, kXMP_ParseMoreBuffers);
+ }
+
+ // The last call has no kXMP_ParseMoreBuffers options, signifying
+ // this is the last input buffer
+ meta.ParseFromBuffer(&rdf[i], (XMP_StringLen)strlen(rdf) - i);
+ return meta;
+
+}
+
+
+/**
+* Writes an XMP packet in XML format to a text file
+*
+* rdf - a pointer to the serialized XMP
+* filename - the name of the file to write to
+*/
+void writeRDFToFile(string * rdf, string filename)
+{
+ ofstream outFile;
+ outFile.open(filename.c_str(), ios::out);
+ outFile << *rdf;
+ outFile.close();
+}
+
+/**
+* Initializes the toolkit and attempts to open a file for updating its metadata.Initially
+* an attempt to open the file is done with a handler, if this fails then the file is opened with
+* packet scanning.Once the file is open several properties are read and displayed in the console.
+*
+* Several properties are then modified, first by checking for their existence and then, if they
+* exist, by updating their values.The updated properties are then displayed again in the console.
+*
+* Next a new XMP object is created from an RDF stream, the properties from the new XMP object are
+* appended to the original XMP object and the updated properties are displayed in the console for
+* last time.
+*
+* The updated XMP object is then serialized in different formats and written to text files.Lastly,
+*the modified XMP is written back to the resource file.
+*/
+
+int main(int argc, const char * argv[])
+{
+ if (argc != 2) // 2 := command and 1 parameter
+ {
+ cout << "usage: ModifyingXMP (filename)" << endl;
+ return 0;
+ }
+
+ string filename = string(argv[1]);
+
+ if (!SXMPMeta::Initialize())
+ {
+ cout << "Could not initialize toolkit!";
+ return -1;
+ }
+
+ XMP_OptionBits options = 0;
+#if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+#endif
+
+ // Must initialize SXMPFiles before we use it
+ if (SXMPFiles::Initialize(options))
+ {
+ try
+ {
+ // Options to open the file with - open for editing and use a smart handler
+ XMP_OptionBits opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUseSmartHandler;
+
+ bool ok;
+ SXMPFiles myFile;
+ std::string status = "";
+
+ // First we try and open the file
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ if (!ok)
+ {
+ status += "No smart handler available for " + filename + "\n";
+ status += "Trying packet scanning.\n";
+
+ // Now try using packet scanning
+ opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning;
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ }
+
+ // If the file is open then read get the XMP data
+ if (ok)
+ {
+ cout << status << endl;
+ cout << filename << " is opened successfully" << endl;
+
+ // Create the xmp object and get the xmp data
+ SXMPMeta meta;
+ myFile.GetXMP(&meta);
+ string buffer;
+ meta.SerializeToBuffer(&buffer);
+ AdobeXMPCore::spIDOMImplementationRegistry DOMRegistry = AdobeXMPCore::IDOMImplementationRegistry::GetDOMImplementationRegistry();
+ AdobeXMPCore::spIDOMParser parser = DOMRegistry->GetParser("rdf");
+ AdobeXMPCore::spIMetadata metaNode = parser->Parse(buffer.c_str(), buffer.size());
+
+ // Display some properties in the console
+ displayPropertyValues(metaNode);
+
+ ///////////////////////////////////////////////////
+ // Now modify the XMP
+ AdobeXMPCore::spISimpleNode simpleNode = metaNode->GetSimpleNode(kXMP_NS_XMP, AdobeXMPCommon::npos, "CreatorTool", AdobeXMPCommon::npos);
+
+ if (simpleNode!=NULL)
+ simpleNode->SetValue("Updated By XMP SDK", AdobeXMPCommon::npos);
+
+ // Update the MetadataDate
+ XMP_DateTime dt;
+ SXMPUtils::CurrentDateTime(&dt);
+ string date;
+ SXMPUtils::ConvertFromDate(dt, &date);
+ AdobeXMPCore::spISimpleNode dateNode = metaNode->GetSimpleNode(kXMP_NS_XMP, AdobeXMPCommon::npos, "MetadataDate", AdobeXMPCommon::npos);
+ if(dateNode != NULL)
+ dateNode->SetValue(date.c_str(), AdobeXMPCommon::npos);
+
+ // Add an item onto the dc:creator array
+ AdobeXMPCore::spIArrayNode arrayNode = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "creator", AdobeXMPCommon::npos);
+
+ // If the array does not exist, it will be created
+ if (arrayNode == NULL)
+ {
+ AdobeXMPCore::spIArrayNode arrayNode = AdobeXMPCore::IArrayNode::CreateUnorderedArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "creator", AdobeXMPCommon::npos);
+ AdobeXMPCore::spINode creatorChild1 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_DC, AdobeXMPCommon::npos, "AuthorName", AdobeXMPCommon::npos, "abc", AdobeXMPCommon::npos);
+ AdobeXMPCore::spINode creatorChild2 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_DC, AdobeXMPCommon::npos, "AnotherAuthorName", AdobeXMPCommon::npos, "xyz", AdobeXMPCommon::npos);
+ arrayNode->AppendNode(creatorChild1);
+ arrayNode->AppendNode(creatorChild2);
+
+ }
+ // If it exists, then just append the nodes to array node
+ else
+ {
+ AdobeXMPCore::spINode creatorChild1 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_DC, AdobeXMPCommon::npos, "AuthorName", AdobeXMPCommon::npos, "abc", AdobeXMPCommon::npos);
+ AdobeXMPCore::spINode creatorChild2 = AdobeXMPCore::ISimpleNode::CreateSimpleNode(kXMP_NS_DC, AdobeXMPCommon::npos, "AnotherAuthorName", AdobeXMPCommon::npos, "xyz", AdobeXMPCommon::npos);
+ arrayNode->AppendNode(creatorChild1);
+ arrayNode->AppendNode(creatorChild2);
+ }
+
+ // Display the properties again to show changes
+ cout << "After update:" << endl;
+ displayPropertyValues(metaNode);
+
+ AdobeXMPCore::spIDOMSerializer serializer = DOMRegistry->GetSerializer("rdf");
+ AdobeXMPCore:: spcINameSpacePrefixMap defaultMap = AdobeXMPCore::INameSpacePrefixMap::GetDefaultNameSpacePrefixMap();
+ std::string serializedPacket = serializer->Serialize(metaNode, defaultMap)->c_str();
+ SXMPMeta fileMeta;
+ fileMeta.ParseFromBuffer(serializedPacket.c_str(), (XMP_StringLen)serializedPacket.length());
+
+ // Create a new XMP object from an RDF string
+ SXMPMeta rdfMeta = createXMPFromRDF();
+
+ // Append the newly created properties onto the original XMP object
+ // This will:
+ // a) Add ANY new TOP LEVEL properties in the source (rdfMeta) to the destination (fileMeta)
+ // b) Replace any top level properties in the source with the matching properties from the destination
+ SXMPUtils::ApplyTemplate(&fileMeta, rdfMeta, kXMPTemplate_AddNewProperties | kXMPTemplate_ReplaceExistingProperties | kXMPTemplate_IncludeInternalProperties);
+
+ // Serialize the packet and write the buffer to a file
+ // Let the padding be computed and use the default linefeed and indents without limits
+ string newBuffer;
+ fileMeta.SerializeToBuffer(&newBuffer);
+
+ // Display the properties again to show changes
+ cout << "After Appending Properties:" << endl;
+ AdobeXMPCore::spIMetadata newMetaNode = parser->Parse(newBuffer.c_str(), newBuffer.size());
+ displayPropertyValues(newMetaNode);
+
+ // Write the packet to a file as RDF
+ writeRDFToFile(&newBuffer, filename + "_XMP_RDF.txt");
+
+ // Check we can put the XMP packet back into the file
+ if (myFile.CanPutXMP(fileMeta))
+ {
+ // If so then update the file with the modified XMP
+ myFile.PutXMP(fileMeta);
+ }
+
+ // Close the SXMPFile. This *must* be called. The XMP is not
+ // actually written and the disk file is not closed until this call is made.
+ myFile.CloseFile();
+ }
+ else
+ {
+ cout << "Unable to open " << filename << endl;
+ }
+
+ }
+ catch (XMP_Error & e)
+ {
+ cout << "ERROR: " << e.GetErrMsg() << endl;
+ }
+
+ // Terminate the toolkit
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ }
+ else
+ {
+ cout << "Could not initialize SXMPFiles.";
+ return -1;
+ }
+
+ return 0;
+}
+
+
diff --git a/samples/source/ReadingXMP.cpp b/samples/source/ReadingXMP.cpp
new file mode 100644
index 0000000..c312535
--- /dev/null
+++ b/samples/source/ReadingXMP.cpp
@@ -0,0 +1,217 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Tutorial solution for the Walkthrough 1 in the XMP Programmers Guide, Opening files and reading XMP.
+* Demonstrates the basic use of the XMPFiles and XMPCore components, obtaining read-only XMP from a file
+* and examining it through the XMP object.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Must be defined to give access to XMPFiles
+#define XMP_INCLUDE_XMPFILES 1
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+#include <iostream>
+#include <fstream>
+
+using namespace std;
+
+
+/**
+* Client defined callback function to dump XMP to a file. In this case an output file stream is used
+* to write a buffer, of length bufferSize, to a text file. This callback is called multiple
+* times during the DumpObject() operation. See the XMP API reference for details of
+* XMP_TextOutputProc() callbacks.
+*/
+XMP_Status DumpXMPToFile(void * refCon, XMP_StringPtr buffer, XMP_StringLen bufferSize)
+{
+ XMP_Status status = 0;
+
+ try
+ {
+ ofstream * outFile = static_cast<ofstream*>(refCon);
+ (*outFile).write(buffer, bufferSize);
+ }
+ catch(XMP_Error & e)
+ {
+ cout << e.GetErrMsg() << endl;
+ return -1; // Return a bad status
+ }
+
+ return status;
+}
+
+/**
+* Initializes the toolkit and attempts to open a file for reading metadata. Initially
+* an attempt to open the file is done with a handler, if this fails then the file is opened with
+* packet scanning. Once the file is open several properties are read and displayed in the console.
+* The XMP object is then dumped to a text file and the resource file is closed.
+*/
+int main ( int argc, const char * argv[] )
+{
+ if ( argc != 2 ) // 2 := command and 1 parameter
+ {
+ cout << "usage: ReadingXMP (filename)" << endl;
+ return 0;
+ }
+
+ string filename = string( argv[1] );
+
+ if(!SXMPMeta::Initialize())
+ {
+ cout << "Could not initialize toolkit!";
+ return -1;
+ }
+ XMP_OptionBits options = 0;
+ #if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+ #endif
+ // Must initialize SXMPFiles before we use it
+ if ( ! SXMPFiles::Initialize ( options ) )
+ {
+ cout << "Could not initialize SXMPFiles.";
+ return -1;
+ }
+
+ try
+ {
+ // Options to open the file with - read only and use a file handler
+ XMP_OptionBits opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUseSmartHandler;
+
+ bool ok;
+ SXMPFiles myFile;
+ std::string status = "";
+
+ // First we try and open the file
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ if( ! ok )
+ {
+ status += "No smart handler available for " + filename + "\n";
+ status += "Trying packet scanning.\n";
+
+ // Now try using packet scanning
+ opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning;
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ }
+
+
+ // If the file is open then read the metadata
+ if(ok)
+ {
+ cout << status << endl;
+ cout << filename << " is opened successfully" << endl;
+ // Create the xmp object and get the xmp data
+ SXMPMeta meta;
+ myFile.GetXMP(&meta);
+
+ bool exists;
+
+ // Read a simple property
+ string simpleValue; //Stores the value for the property
+ exists = meta.GetProperty(kXMP_NS_XMP, "CreatorTool", &simpleValue, NULL);
+ if(exists)
+ cout << "CreatorTool = " << simpleValue << endl;
+ else
+ simpleValue.clear();
+
+ // Get the first element in the dc:creator array
+ string elementValue;
+ exists = meta.GetArrayItem(kXMP_NS_DC, "creator", 1, &elementValue, NULL);
+ if(exists)
+ cout << "dc:creator = " << elementValue << endl;
+ else
+ elementValue.clear();
+
+ // Get the the entire dc:subject array
+ string propValue;
+ int arrSize = meta.CountArrayItems(kXMP_NS_DC, "subject");
+ for(int i = 1; i <= arrSize;i++)
+ {
+ meta.GetArrayItem(kXMP_NS_DC, "subject", i, &propValue, 0);
+ cout << "dc:subject[" << i << "] = " << propValue << endl;
+ }
+
+ // Get the dc:title for English and French
+ string itemValue;
+ string actualLang;
+ meta.GetLocalizedText(kXMP_NS_DC, "title", "en", "en-US", NULL, &itemValue, NULL);
+ cout << "dc:title in English = " << itemValue << endl;
+
+ meta.GetLocalizedText(kXMP_NS_DC, "title", "fr", "fr-FR", NULL, &itemValue, NULL);
+ cout << "dc:title in French = " << itemValue << endl;
+
+ // Get dc:MetadataDate
+ XMP_DateTime myDate;
+ if(meta.GetProperty_Date(kXMP_NS_XMP, "MetadataDate", &myDate, NULL))
+ {
+ // Convert the date struct into a convenient string and display it
+ string myDateStr;
+ SXMPUtils::ConvertFromDate(myDate, &myDateStr);
+ cout << "meta:MetadataDate = " << myDateStr << endl;
+ }
+
+ // See if the flash struct exists and see if it was used
+ string path, value;
+ exists = meta.DoesStructFieldExist(kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF,"Fired");
+ if(exists)
+ {
+ bool flashFired;
+ SXMPUtils::ComposeStructFieldPath(kXMP_NS_EXIF, "Flash", kXMP_NS_EXIF, "Fired", &path);
+ meta.GetProperty_Bool(kXMP_NS_EXIF, path.c_str(), &flashFired, NULL);
+ string flash = (flashFired) ? "True" : "False";
+
+ cout << "Flash Used = " << flash << endl;
+ }
+
+
+
+ // Dump the current xmp object to a file
+ ofstream dumpFile;
+ dumpFile.open("XMPDump.txt", ios::out);
+ meta.DumpObject(DumpXMPToFile, &dumpFile);
+ dumpFile.close();
+ cout << endl << "XMP dumped to XMPDump.txt" << endl;
+
+ // Close the SXMPFile. The resource file is already closed if it was
+ // opened as read only but this call must still be made.
+ myFile.CloseFile();
+ }
+ else
+ {
+ cout << "Unable to open " << filename << endl;
+ }
+ }
+ catch(XMP_Error & e)
+ {
+ cout << "ERROR: " << e.GetErrMsg() << endl;
+ }
+
+ // Terminate the toolkit
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ return 0;
+}
+
diff --git a/samples/source/ReadingXMPNewDOM.cpp b/samples/source/ReadingXMPNewDOM.cpp
new file mode 100644
index 0000000..99d636a
--- /dev/null
+++ b/samples/source/ReadingXMPNewDOM.cpp
@@ -0,0 +1,286 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Tutorial solution for the Walkthrough 1 in the XMP Programmers Guide, Opening files and reading XMP.
+* Demonstrates the basic use of the XMPFiles and XMPCore components, obtaining read-only XMP from a file
+* and examining it through the XMP object.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Must be defined to give access to XMPFiles
+#define XMP_INCLUDE_XMPFILES 1
+
+#define ENABLE_NEW_DOM_MODEL 1
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+#include "XMPCore/Interfaces/IDOMImplementationRegistry.h"
+#include "XMPCore/Interfaces/IDOMParser.h"
+#include "XMPCore/Interfaces/IDOMSerializer.h"
+#include "XMPCore/Interfaces/IMetadata.h"
+#include "XMPCore/Interfaces/ICoreObjectFactory.h"
+#include "XMPCore/Interfaces/ISimpleNode.h"
+#include "XMPCore/Interfaces/IStructureNode.h"
+#include "XMPCore/Interfaces/IArrayNode.h"
+#include "XMPCore/Interfaces/INameSpacePrefixMap.h"
+#include "XMPCommon/Interfaces/IUTF8String.h"
+#include "XMPCore/Interfaces/INodeIterator.h"
+
+using namespace std;
+using namespace AdobeXMPCore;
+
+
+void GetLocalizedText(spIArrayNode titleNode, const char* specificLang, const char* genericLang, string lang)
+{
+ AdobeXMPCore::spINode currItem;
+ const size_t itemLim = titleNode->ChildCount();
+ size_t itemNum;
+
+ spISimpleNode xmlLangQualifierNode, currItemNode;
+ for (itemNum = 1; itemNum <= itemLim; ++itemNum)
+ {
+ currItem = titleNode->GetNodeAtIndex(itemNum);
+ if (currItem != NULL)
+ {
+ xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode();
+ if (!strcmp(xmlLangQualifierNode->GetValue()->c_str(), specificLang)) {
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" <<" " << lang <<" " << currItemNode->GetValue()->c_str() << endl;
+ return;
+ }
+ }
+ }
+
+ if (*genericLang != 0)
+ {
+ // Look for the first partial match with the generic language.
+ const size_t genericLen = strlen(genericLang);
+ for (itemNum = 1; itemNum <= itemLim; ++itemNum) {
+ currItem = titleNode->GetNodeAtIndex(itemNum);
+ xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode();
+ XMP_StringPtr currLang = xmlLangQualifierNode->GetValue()->c_str();
+ const size_t currLangSize = xmlLangQualifierNode->GetValue()->size();
+ if ((currLangSize >= genericLen) &&
+ !strncmp(currLang, genericLang, genericLen) &&
+ ((currLangSize == genericLen) || (currLang[genericLen] == '-')))
+ {
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" <<" " << lang << " " << currItemNode->GetValue()->c_str() << endl;
+ return;
+ }
+ }
+ }
+
+ // Look for an 'x-default' item.
+ for (itemNum = 1; itemNum <= itemLim; ++itemNum) {
+ currItem = titleNode->GetNodeAtIndex(itemNum);
+ xmlLangQualifierNode = currItem->QualifiersIterator()->GetNode()->ConvertToSimpleNode();
+ if (!strcmp(xmlLangQualifierNode->GetValue()->c_str(), "x-default")) {
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" <<" " << lang <<" " << currItemNode->GetValue()->c_str() << endl;
+ return;
+ }
+ }
+
+ // Everything failed, choose the first item.
+ currItem = titleNode->GetNodeAtIndex(1);
+ currItemNode = currItem->ConvertToSimpleNode();
+ cout << "dc:title in" <<" " <<lang << " "<< currItemNode->GetValue() << endl;
+ return;
+
+}
+
+
+
+void writeRDFToFile(string * rdf, string filename)
+{
+ ofstream outFile;
+ outFile.open(filename.c_str(), ios::out);
+ outFile << *rdf;
+ outFile.close();
+}
+
+
+/**
+* Initializes the toolkit and attempts to open a file for reading metadata.Initially
+* an attempt to open the file is done with a handler, if this fails then the file is opened with
+* packet scanning.Once the file is open several properties are read and displayed in the console.
+* The XMP object is then dumped to a text file and the resource file is closed.
+*/
+
+int main(int argc, const char * argv[])
+{
+
+ if (argc != 2) // 2 := command and 1 parameter
+ {
+ cout << "usage: ReadingXMP (filename)" << endl;
+ return 0;
+ }
+
+ string filename = string(argv[1]);
+
+ if (!SXMPMeta::Initialize())
+ {
+ cout << "Could not initialize toolkit!";
+ return -1;
+ }
+ XMP_OptionBits options = 0;
+#if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+#endif
+ // Must initialize SXMPFiles before we use it
+ if (!SXMPFiles::Initialize(options))
+ {
+ cout << "Could not initialize SXMPFiles.";
+ return -1;
+ }
+
+ try
+ {
+ // Options to open the file with - read only and use a file handler
+ XMP_OptionBits opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUseSmartHandler;
+
+ bool ok;
+ SXMPFiles myFile;
+ std::string status = "";
+
+ // First we try and open the file
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ if (!ok)
+ {
+ status += "No smart handler available for " + filename + "\n";
+ status += "Trying packet scanning.\n";
+
+ // Now try using packet scanning
+ opts = kXMPFiles_OpenForUpdate | kXMPFiles_OpenUsePacketScanning;
+ ok = myFile.OpenFile(filename, kXMP_UnknownFile, opts);
+ }
+
+ // If the file is open then read the metadata
+ if (ok)
+ {
+
+ cout << status << endl;
+ cout << filename << " is opened successfully" << endl;
+ // Create the xmp object and get the xmp data
+ SXMPMeta meta;
+ myFile.GetXMP(&meta);
+ string buffer;
+ meta.SerializeToBuffer(&buffer);
+ writeRDFToFile(&buffer, "Image1RDF.txt");
+ AdobeXMPCore::spIDOMImplementationRegistry DOMRegistry = AdobeXMPCore::IDOMImplementationRegistry::GetDOMImplementationRegistry();
+ AdobeXMPCore:: spIDOMParser parser = DOMRegistry->GetParser("rdf");
+ AdobeXMPCore ::spIMetadata metaNode = parser->Parse(buffer.c_str(), buffer.size());
+
+ // Read a simple property
+ AdobeXMPCore::spISimpleNode simpleNode = metaNode->GetSimpleNode(kXMP_NS_XMP, AdobeXMPCommon::npos, "CreatorTool", AdobeXMPCommon::npos);
+ if (simpleNode != NULL)
+ {
+ string simpleNodeValue = simpleNode->GetValue()->c_str();
+ cout << "CreatorTool = " << simpleNodeValue << endl;
+ }
+
+ // Get the first element in the dc:creator array
+ AdobeXMPCore::spIArrayNode arrayNode = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "creator", AdobeXMPCommon::npos);
+ if (arrayNode != NULL)
+ {
+ AdobeXMPCore::spISimpleNode arrayNodeChild = arrayNode->GetSimpleNodeAtIndex(1);
+ if (arrayNodeChild != NULL)
+ {
+ string arrayNodeChildValue = arrayNodeChild->GetValue()->c_str();
+ cout << "dc:creator = " << arrayNodeChildValue << endl;
+ }
+ }
+
+ // Get the the entire dc:subject array
+ AdobeXMPCore::spIArrayNode subjectArray = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "subject", AdobeXMPCommon::npos);
+ if (subjectArray != NULL)
+ {
+ sizet arraySize = subjectArray->ChildCount();
+ for (sizet i = 1; i <= arraySize; i++)
+ {
+ AdobeXMPCore::spISimpleNode subjectChild = subjectArray->GetSimpleNodeAtIndex(i);
+ if (subjectChild != NULL)
+ {
+ string propValue = subjectChild->GetValue()->c_str();
+ cout << "dc:subject[" << i << "] = " << propValue << endl;
+ }
+ }
+ }
+
+ // Get the dc:title for English and French
+
+
+ AdobeXMPCore::spIArrayNode titleNode = metaNode->GetArrayNode(kXMP_NS_DC, AdobeXMPCommon::npos, "title", AdobeXMPCommon::npos);
+ if (titleNode != NULL)
+ {
+ GetLocalizedText(titleNode, "en-US", "en", "English");
+ GetLocalizedText(titleNode, "fr-FR", "fr", "French");
+ }
+
+
+
+ // Get dc:MetadataDate
+ AdobeXMPCore::spISimpleNode dateNode = metaNode->GetSimpleNode(kXMP_NS_XMP, AdobeXMPCommon::npos, "MetadataDate", AdobeXMPCommon::npos);
+ if(dateNode != NULL)
+ {
+ string date = dateNode->GetValue()->c_str();
+ cout << "meta:MetadataDate = " << date << endl;
+ }
+
+
+ // See if the flash struct exists and see if it was used
+ AdobeXMPCore::spIStructureNode flashNode = metaNode->GetStructureNode(kXMP_NS_EXIF, AdobeXMPCommon::npos, "Flash", AdobeXMPCommon::npos);
+ if (flashNode != NULL)
+ {
+ AdobeXMPCore::spISimpleNode field = flashNode->GetSimpleNode(kXMP_NS_EXIF, AdobeXMPCommon::npos, "Fired", AdobeXMPCommon::npos);
+ if (field != NULL)
+ {
+ string fieldValue = field->GetValue()->c_str();
+ cout << "Flash Used = " << fieldValue << endl;
+ }
+ }
+ // Close the SXMPFile. The resource file is already closed if it was
+ // opened as read only but this call must still be made.
+ myFile.CloseFile();
+
+ }
+ else
+ {
+ cout << "Unable to open " << filename << endl;
+ }
+ }
+ catch (XMP_Error & e)
+ {
+ cout << "ERROR: " << e.GetErrMsg() << endl;
+ }
+
+ // Terminate the toolkit
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ return 0;
+}
diff --git a/samples/source/UnicodeCorrectness.cpp b/samples/source/UnicodeCorrectness.cpp
new file mode 100644
index 0000000..2864358
--- /dev/null
+++ b/samples/source/UnicodeCorrectness.cpp
@@ -0,0 +1,2815 @@
+// =================================================================================================
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <ctime>
+
+#include <cstdlib>
+#include <cerrno>
+#include <stdexcept>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+using namespace std;
+
+#if WIN_ENV
+ #pragma warning ( disable : 4701 ) // local variable may be used without having been initialized
+#endif
+
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h"
+#include "public/include/XMP_Const.h"
+
+#include "source/EndianUtils.hpp"
+#include "source/UnicodeConversions.hpp"
+#include "source/UnicodeConversions.cpp"
+
+// =================================================================================================
+
+#define kCodePointCount 0x110000
+
+UTF8Unit sU8 [kCodePointCount*4 + 8];
+UTF16Unit sU16 [kCodePointCount*2 + 4];
+UTF32Unit sU32 [kCodePointCount + 2];
+
+// =================================================================================================
+
+static UTF16Unit NativeUTF16BE ( UTF16Unit value )
+{
+ if ( ! kBigEndianHost ) SwapUTF16 ( &value, &value, 1 );
+ return value;
+}
+
+static UTF16Unit NativeUTF16LE ( UTF16Unit value )
+{
+ if ( kBigEndianHost ) SwapUTF16 ( &value, &value, 1 );
+ return value;
+}
+
+static UTF32Unit NativeUTF32BE ( UTF32Unit value )
+{
+ if ( ! kBigEndianHost ) SwapUTF32 ( &value, &value, 1 );
+ return value;
+}
+
+static UTF32Unit NativeUTF32LE ( UTF32Unit value )
+{
+ if ( kBigEndianHost ) SwapUTF32 ( &value, &value, 1 );
+ return value;
+}
+
+// =================================================================================================
+
+static void Bad_CodePoint_to_UTF8 ( FILE * log, UTF32Unit cp )
+{
+ UTF8Unit u8[8];
+ size_t len;
+
+ try {
+ CodePoint_to_UTF8 ( cp, u8, sizeof(u8), &len );
+ fprintf ( log, " *** CodePoint_to_UTF8 failure, no exception for 0x%X\n", cp );
+ } catch ( ... ) {
+ // Do nothing, the exception is expected.
+ }
+
+}
+
+// =================================================================================================
+
+static void Bad_CodePoint_to_UTF16BE ( FILE * log, UTF32Unit cp )
+{
+ UTF16Unit u16[4];
+ size_t len;
+
+ try {
+ CodePoint_to_UTF16BE ( cp, u16, sizeof(u16), &len );
+ fprintf ( log, " *** CodePoint_to_UTF16BE failure, no exception for 0x%X\n", cp );
+ } catch ( ... ) {
+ // Do nothing, the exception is expected.
+ }
+
+}
+
+// =================================================================================================
+
+static void Bad_CodePoint_to_UTF16LE ( FILE * log, UTF32Unit cp )
+{
+ UTF16Unit u16[4];
+ size_t len;
+
+ try {
+ CodePoint_to_UTF16LE ( cp, u16, sizeof(u16), &len );
+ fprintf ( log, " *** CodePoint_to_UTF16LE failure, no exception for 0x%X\n", cp );
+ } catch ( ... ) {
+ // Do nothing, the exception is expected.
+ }
+
+}
+
+// =================================================================================================
+
+static void Bad_CodePoint_from_UTF8 ( FILE * log, const char * inU8, const char * message )
+{
+ UTF32Unit cp;
+ size_t len;
+
+ try {
+ CodePoint_from_UTF8 ( (UTF8Unit*)inU8, strlen(inU8), &cp, &len );
+ fprintf ( log, " *** CodePoint_from_UTF8 failure, no exception for %s\n", message );
+ } catch ( ... ) {
+ // Do nothing, the exception is expected.
+ }
+
+}
+
+// =================================================================================================
+
+static void Bad_CodePoint_from_UTF16BE ( FILE * log, const UTF16Unit * inU16, const size_t inLen, const char * message )
+{
+ UTF32Unit cp;
+ size_t outLen;
+
+ try {
+ CodePoint_from_UTF16BE ( inU16, inLen, &cp, &outLen );
+ fprintf ( log, " *** CodePoint_from_UTF16BE failure, no exception for %s\n", message );
+ } catch ( ... ) {
+ // Do nothing, the exception is expected.
+ }
+
+}
+
+// =================================================================================================
+
+static void Bad_CodePoint_from_UTF16LE ( FILE * log, const UTF16Unit * inU16, const size_t inLen, const char * message )
+{
+ UTF32Unit cp;
+ size_t outLen;
+
+ try {
+ CodePoint_from_UTF16LE ( inU16, inLen, &cp, &outLen );
+ fprintf ( log, " *** CodePoint_from_UTF16LE failure, no exception for %s\n", message );
+ } catch ( ... ) {
+ // Do nothing, the exception is expected.
+ }
+
+}
+
+// =================================================================================================
+
+static void Test_SwappingPrimitives ( FILE * log )
+{
+ UTF16Unit u16[8];
+ UTF32Unit u32[8];
+ UTF32Unit i;
+
+ fprintf ( log, "\nTesting byte swapping primitives\n" );
+
+ u16[0] = 0x1122;
+ if ( UTF16InSwap(&u16[0]) == 0x2211 ) printf ( " UTF16InSwap OK\n" );
+
+ u32[0] = 0x11223344;
+ if ( UTF32InSwap(&u32[0]) == 0x44332211 ) printf ( " UTF32InSwap OK\n" );
+
+ UTF16OutSwap ( &u16[0], 0x1122 );
+ if ( u16[0] == 0x2211 ) printf ( " UTF16OutSwap OK\n" );
+
+ UTF32OutSwap ( &u32[0], 0x11223344 );
+ if ( u32[0] == 0x44332211 ) printf ( " UTF32OutSwap OK\n" );
+
+ for ( i = 0; i < 8; ++i ) u16[i] = 0x1100 | UTF16Unit(i);
+ SwapUTF16 ( u16, u16, 8 );
+ for ( i = 0; i < 8; ++i ) {
+ if ( u16[i] != ((UTF16Unit(i) << 8) | 0x11) ) break;
+ }
+ if ( i == 8 ) printf ( " SwapUTF16 OK\n" );
+
+ for ( i = 0; i < 8; ++i ) u32[i] = 0x11223300 | i;
+ SwapUTF32 ( u32, u32, 8 );
+ for ( i = 0; i < 8; ++i ) {
+ if ( u32[i] != ((i << 24) | 0x00332211) ) break;
+ }
+ if ( i == 8 ) printf ( " SwapUTF32 OK\n" );
+
+} // Test_SwappingPrimitives
+
+// =================================================================================================
+
+static void Test_CodePoint_to_UTF8 ( FILE * log )
+{
+ size_t len, lenx;
+ UTF32Unit cp, cp0, cpx;
+ UTF8Unit u8[8];
+
+ // -------------------------------------
+ // Test CodePoint_to_UTF8 on good input.
+
+ fprintf ( log, "\nTesting CodePoint_to_UTF8 on good input\n" );
+
+ // Test ASCII, 00..7F.
+ cp0 = 0;
+ for ( cp = cp0; cp < 0x80; ++cp ) {
+ CodePoint_to_UTF8 ( cp, u8, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 1, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 1) || (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 2, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 1) || (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 2 byte values, 0080..07FF : 110x xxxx 10xx xxxx
+ cp0 = cpx+1;
+ for ( cp = cp0; cp < 0x800; ++cp ) {
+ CodePoint_to_UTF8 ( cp, u8, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 1, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 2, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 2) || (cp != cpx) || (lenx != 2) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 3, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 2) || (cp != cpx) || (lenx != 2) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 3 byte values, 0800..D7FF : 1110 xxxx 10xx xxxx 10xx xxxx
+ cp0 = cpx+1;
+ for ( cp = cp0; cp < 0xD800; ++cp ) {
+ CodePoint_to_UTF8 ( cp, u8, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 1, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 2, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 3, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 3) || (cp != cpx) || (lenx != 3) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 4, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 3) || (cp != cpx) || (lenx != 3) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 3 byte values, E000..FFFF : 1110 xxxx 10xx xxxx 10xx xxxx
+ cp0 = 0xE000;
+ for ( cp = cp0; cp < 0x10000; ++cp ) {
+ CodePoint_to_UTF8 ( cp, u8, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 1, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 2, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 3, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 3) || (cp != cpx) || (lenx != 3) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 4, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 3) || (cp != cpx) || (lenx != 3) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 4 byte values, 10000..10FFFF : 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ cp0 = cpx+1;
+ for ( cp = cp0; cp < 0x110000; ++cp ) {
+ CodePoint_to_UTF8 ( cp, u8, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 1, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 2, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 3, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 4, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 4) || (cp != cpx) || (lenx != 4) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ CodePoint_to_UTF8 ( cp, u8, 5, &len );
+ CodePoint_from_UTF8 ( u8, len, &cpx, &lenx );
+ if ( (len != 4) || (cp != cpx) || (lenx != 4) ) fprintf ( log, " *** CodePoint_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // --------------------------------------
+ // Test CodePoint_to_UTF8 with bad input.
+
+ fprintf ( log, "\nTesting CodePoint_to_UTF8 with bad input\n" );
+
+ Bad_CodePoint_to_UTF8 ( log, 0x110000 ); // Code points beyond the defined range.
+ Bad_CodePoint_to_UTF8 ( log, 0x123456 );
+ Bad_CodePoint_to_UTF8 ( log, 0xFFFFFFFF );
+ Bad_CodePoint_to_UTF8 ( log, 0xD800 ); // Surrogate code points.
+ Bad_CodePoint_to_UTF8 ( log, 0xDC00 );
+ Bad_CodePoint_to_UTF8 ( log, 0xDFFF );
+
+ fprintf ( log, " CodePoint_to_UTF8 done with bad input\n" );
+
+} // Test_CodePoint_to_UTF8
+
+// =================================================================================================
+
+static void Test_CodePoint_from_UTF8 ( FILE * log )
+{
+ UTF32Unit i, j, k, l;
+ size_t len;
+ UTF32Unit cp, cp0, cpx;
+ UTF8Unit u8[5];
+
+ // ---------------------------------------
+ // Test CodePoint_from_UTF8 on good input.
+
+ fprintf ( log, "\nTesting CodePoint_from_UTF8 on good input\n" );
+
+ // Test ASCII, 00..7F.
+ cp0 = 0;
+ for ( i = 0; i < 0x80; ++i ) {
+ u8[0] = UTF8Unit(i); u8[1] = 0xFF; cpx = i;
+ CodePoint_from_UTF8 ( u8, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, "CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 1, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ }
+ fprintf ( log, " CodePoint_from_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 2 byte values, 0080..07FF : 110x xxxx 10xx xxxx
+ cp0 = cpx+1;
+ for ( i = 0; i < 0x20; ++i ) {
+ for ( j = 0; j < 0x40; ++j ) {
+ cpx = (i<<6) + j; if ( cpx < cp0 ) continue;
+ u8[0] = 0xC0+UTF8Unit(i); u8[1] = 0x80+UTF8Unit(j); u8[2] = 0xFF;
+ CodePoint_from_UTF8 ( u8, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 1, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 2) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 3, &cp, &len );
+ if ( (cp != cpx) || (len != 2) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ }
+ }
+ fprintf ( log, " CodePoint_from_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 3 byte values, 0800..D7FF : 1110 xxxx 10xx xxxx 10xx xxxx
+ cp0 = cpx+1;
+ for ( i = 0; i < 0x10; ++i ) {
+ for ( j = 0; j < 0x40; ++j ) {
+ for ( k = 0; k < 0x40; ++k ) {
+ cpx = (i<<12) + (j<<6) + k; if ( cpx < cp0 ) continue;
+ u8[0] = 0xE0+UTF8Unit(i); u8[1] = 0x80+UTF8Unit(j); u8[2] = 0x80+UTF8Unit(k); u8[3] = 0xFF;
+ CodePoint_from_UTF8 ( u8, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 1, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 2, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 3, &cp, &len );
+ if ( (cp != cpx) || (len != 3) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 4, &cp, &len );
+ if ( (cp != cpx) || (len != 3) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ }
+ if ( cpx == 0xD7FF ) break;
+ }
+ if ( cpx == 0xD7FF ) break;
+ }
+ fprintf ( log, " CodePoint_from_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 3 byte values, E000..FFFF : 1110 xxxx 10xx xxxx 10xx xxxx
+ cp0 = 0xE000;
+ for ( i = 0; i < 0x10; ++i ) {
+ for ( j = 0; j < 0x40; ++j ) {
+ for ( k = 0; k < 0x40; ++k ) {
+ cpx = (i<<12) + (j<<6) + k; if ( cpx < cp0 ) continue;
+ u8[0] = 0xE0+UTF8Unit(i); u8[1] = 0x80+UTF8Unit(j); u8[2] = 0x80+UTF8Unit(k); u8[3] = 0xFF;
+ CodePoint_from_UTF8 ( u8, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 1, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 2, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 3, &cp, &len );
+ if ( (cp != cpx) || (len != 3) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 4, &cp, &len );
+ if ( (cp != cpx) || (len != 3) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ }
+ }
+ }
+ fprintf ( log, " CodePoint_from_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test 4 byte values, 10000..10FFFF : 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
+ cp0 = cpx+1;
+ for ( i = 0; i < 0x7; ++i ) {
+ for ( j = 0; j < 0x40; ++j ) {
+ for ( k = 0; k < 0x40; ++k ) {
+ for ( l = 0; l < 0x40; ++l ) {
+ cpx = (i<<18) + (j<<12) + (k<<6) + l; if ( cpx < cp0 ) continue;
+ u8[0] = 0xF0+UTF8Unit(i); u8[1] = 0x80+UTF8Unit(j); u8[2] = 0x80+UTF8Unit(k); u8[3] = 0x80+UTF8Unit(l); u8[4] = 0xFF;
+ CodePoint_from_UTF8 ( u8, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 1, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 2, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 3, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 4, &cp, &len );
+ if ( (cp != cpx) || (len != 4) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF8 ( u8, 5, &cp, &len );
+ if ( (cp != cpx) || (len != 4) ) fprintf ( log, " *** CodePoint_from_UTF8 failure for U+%.4X\n", cpx );
+ }
+ if ( cpx == 0x10FFFF ) break;
+ }
+ if ( cpx == 0x10FFFF ) break;
+ }
+ if ( cpx == 0x10FFFF ) break;
+ }
+ fprintf ( log, " CodePoint_from_UTF8 done for %.4X..%.4X\n", cp0, cpx );
+
+ // ----------------------------------------
+ // Test CodePoint_from_UTF8 with bad input.
+
+ fprintf ( log, "\nTesting CodePoint_from_UTF8 with bad input\n" );
+
+ Bad_CodePoint_from_UTF8 ( log, "\x88\x20", "bad leading byte count" ); // One byte "sequence".
+ Bad_CodePoint_from_UTF8 ( log, "\xF9\x90\x80\x80\x80\x20", "bad leading byte count" ); // Five byte sequence.
+ Bad_CodePoint_from_UTF8 ( log, "\xFE\x90\x80\x80\x80\x80\x80\x20", "bad leading byte count" ); // Seven byte sequence.
+ Bad_CodePoint_from_UTF8 ( log, "\xFF\x90\x80\x80\x80\x80\x80\x80\x20", "bad leading byte count" ); // Eight byte sequence.
+
+ Bad_CodePoint_from_UTF8 ( log, "\xF1\x80\x01\x80\x20", "bad following high bits" ); // 00xx xxxx
+ Bad_CodePoint_from_UTF8 ( log, "\xF1\x80\x40\x80\x20", "bad following high bits" ); // 01xx xxxx
+ Bad_CodePoint_from_UTF8 ( log, "\xF1\x80\xC0\x80\x20", "bad following high bits" ); // 11xx xxxx
+
+ Bad_CodePoint_from_UTF8 ( log, "\xF4\x90\x80\x80\x20", "out of range code point" ); // U+110000
+ Bad_CodePoint_from_UTF8 ( log, "\xF7\xBF\xBF\xBF\x20", "out of range code point" ); // U+1FFFFF
+
+ Bad_CodePoint_from_UTF8 ( log, "\xED\xA0\x80\x20", "surrogate code point" ); // U+D800
+ Bad_CodePoint_from_UTF8 ( log, "\xED\xB0\x80\x20", "surrogate code point" ); // U+DC00
+ Bad_CodePoint_from_UTF8 ( log, "\xED\xBF\xBF\x20", "surrogate code point" ); // U+DFFF
+
+ fprintf ( log, " CodePoint_from_UTF8 done with bad input\n" );
+
+} // Test_CodePoint_from_UTF8
+
+// =================================================================================================
+
+static void Test_CodePoint_to_UTF16 ( FILE * log )
+{
+ size_t len, lenx;
+ UTF32Unit cp, cp0, cpx;
+ UTF16Unit u16[3];
+
+ // ----------------------------------------
+ // Test CodePoint_to_UTF16BE on good input.
+
+ fprintf ( log, "\nTesting CodePoint_to_UTF16BE on good input\n" );
+
+ // Some explicit sanity tests, in case the code and exhaustive tests have inverse bugs.
+ if ( kBigEndianHost ) {
+ CodePoint_to_UTF16BE ( 0x1234, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0x1234) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+1234\n" );
+ CodePoint_to_UTF16BE ( 0xFEDC, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0xFEDC) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+FEDC\n" );
+ CodePoint_to_UTF16BE ( 0x14834, u16, 2, &len );
+ if ( (len != 2) || (u16[0] != 0xD812) || (u16[1] != 0xDC34) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+14834\n" );
+ } else {
+ CodePoint_to_UTF16BE ( 0x1234, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0x3412) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+1234\n" );
+ CodePoint_to_UTF16BE ( 0xFEDC, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0xDCFE) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+FEDC\n" );
+ CodePoint_to_UTF16BE ( 0x14834, u16, 2, &len );
+ if ( (len != 2) || (u16[0] != 0x12D8) || (u16[1] != 0x34DC) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+14834\n" );
+ }
+ fprintf ( log, " CodePoint_to_UTF16BE sanity tests done\n" );
+
+ // Test the low part of the BMP, 0000..D7FF.
+ cp0 = 0;
+ for ( cp = cp0; cp < 0xD800; ++cp ) {
+ CodePoint_to_UTF16BE ( cp, u16, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 1, &len );
+ if ( (len != 1) || (NativeUTF16BE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16BE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 2, &len );
+ if ( (len != 1) || (NativeUTF16BE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16BE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF16BE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test the high part of the BMP, E000..FFFF.
+ cp0 = 0xE000;
+ for ( cp = cp0; cp < 0x10000; ++cp ) {
+ CodePoint_to_UTF16BE ( cp, u16, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 1, &len );
+ if ( (len != 1) || (NativeUTF16BE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16BE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 2, &len );
+ if ( (len != 1) || (NativeUTF16BE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16BE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF16BE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test beyond the BMP, 10000..10FFFF.
+ cp0 = 0x10000;
+ for ( cp = cp0; cp < 0x110000; ++cp ) {
+ CodePoint_to_UTF16BE ( cp, u16, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 1, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 2, &len );
+ if ( (len != 2) ||
+ (NativeUTF16BE(u16[0]) != (0xD800 | ((cp-0x10000) >> 10))) ||
+ (NativeUTF16BE(u16[1]) != (0xDC00 | ((cp-0x10000) & 0x3FF))) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16BE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 2) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16BE ( cp, u16, 3, &len );
+ if ( (len != 2) ||
+ (NativeUTF16BE(u16[0]) != (0xD800 | ((cp-0x10000) >> 10))) ||
+ (NativeUTF16BE(u16[1]) != (0xDC00 | ((cp-0x10000) & 0x3FF))) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16BE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 2) ) fprintf ( log, " *** CodePoint_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF16BE done for %.4X..%.4X\n", cp0, cpx );
+
+ // ----------------------------------------
+ // Test CodePoint_to_UTF16LE on good input.
+
+ fprintf ( log, "\nTesting CodePoint_to_UTF16LE on good input\n" );
+
+ // Some explicit sanity tests, in case the code and exhaustive tests have inverse bugs.
+ if ( kBigEndianHost ) {
+ CodePoint_to_UTF16LE ( 0x1234, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0x3412) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+1234\n" );
+ CodePoint_to_UTF16LE ( 0xFEDC, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0xDCFE) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+FEDC\n" );
+ CodePoint_to_UTF16LE ( 0x14834, u16, 2, &len );
+ if ( (len != 2) || (u16[0] != 0x12D8) || (u16[1] != 0x34DC) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+14834\n" );
+ } else {
+ CodePoint_to_UTF16LE ( 0x1234, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0x1234) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+1234\n" );
+ CodePoint_to_UTF16LE ( 0xFEDC, u16, 1, &len );
+ if ( (len != 1) || (u16[0] != 0xFEDC) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+FEDC\n" );
+ CodePoint_to_UTF16LE ( 0x14834, u16, 2, &len );
+ if ( (len != 2) || (u16[0] != 0xD812) || (u16[1] != 0xDC34) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+14834\n" );
+ }
+ fprintf ( log, " CodePoint_to_UTF16LE sanity tests done\n" );
+
+ // Test the low part of the BMP, 0000..D7FF.
+ cp0 = 0;
+ for ( cp = cp0; cp < 0xD800; ++cp ) {
+ CodePoint_to_UTF16LE ( cp, u16, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 1, &len );
+ if ( (len != 1) || (NativeUTF16LE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16LE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 2, &len );
+ if ( (len != 1) || (NativeUTF16LE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16LE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF16LE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test the high part of the BMP, E000..FFFF.
+ cp0 = 0xE000;
+ for ( cp = cp0; cp < 0x10000; ++cp ) {
+ CodePoint_to_UTF16LE ( cp, u16, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 1, &len );
+ if ( (len != 1) || (NativeUTF16LE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16LE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 2, &len );
+ if ( (len != 1) || (NativeUTF16LE(u16[0]) != cp) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16LE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 1) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF16LE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test beyond the BMP, 10000..10FFFF.
+ cp0 = 0x10000;
+ for ( cp = cp0; cp < 0x110000; ++cp ) {
+ CodePoint_to_UTF16LE ( cp, u16, 0, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 1, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 2, &len );
+ if ( (len != 2) ||
+ (NativeUTF16LE(u16[0]) != (0xD800 | ((cp-0x10000) >> 10))) ||
+ (NativeUTF16LE(u16[1]) != (0xDC00 | ((cp-0x10000) & 0x3FF))) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16LE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 2) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_to_UTF16LE ( cp, u16, 3, &len );
+ if ( (len != 2) ||
+ (NativeUTF16LE(u16[0]) != (0xD800 | ((cp-0x10000) >> 10))) ||
+ (NativeUTF16LE(u16[1]) != (0xDC00 | ((cp-0x10000) & 0x3FF))) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ CodePoint_from_UTF16LE ( u16, len, &cpx, &lenx );
+ if ( (cp != cpx) || (lenx != 2) ) fprintf ( log, " *** CodePoint_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ fprintf ( log, " CodePoint_to_UTF16LE done for %.4X..%.4X\n", cp0, cpx );
+
+ // ---------------------------------------
+ // Test CodePoint_to_UTF16 with bad input.
+
+ fprintf ( log, "\nTesting CodePoint_to_UTF16 with bad input\n" );
+
+ Bad_CodePoint_to_UTF16BE ( log, 0x110000 ); // Code points beyond the defined range.
+ Bad_CodePoint_to_UTF16BE ( log, 0x123456 );
+ Bad_CodePoint_to_UTF16BE ( log, 0xFFFFFFFF );
+ Bad_CodePoint_to_UTF16BE ( log, 0xD800 ); // Surrogate code points.
+ Bad_CodePoint_to_UTF16BE ( log, 0xDC00 );
+ Bad_CodePoint_to_UTF16BE ( log, 0xDFFF );
+
+ fprintf ( log, " CodePoint_to_UTF16BE done with bad input\n" );
+
+ Bad_CodePoint_to_UTF16LE ( log, 0x110000 ); // Code points beyond the defined range.
+ Bad_CodePoint_to_UTF16LE ( log, 0x123456 );
+ Bad_CodePoint_to_UTF16LE ( log, 0xFFFFFFFF );
+ Bad_CodePoint_to_UTF16LE ( log, 0xD800 ); // Surrogate code points.
+ Bad_CodePoint_to_UTF16LE ( log, 0xDC00 );
+ Bad_CodePoint_to_UTF16LE ( log, 0xDFFF );
+
+ fprintf ( log, " CodePoint_to_UTF16LE done with bad input\n" );
+
+} // Test_CodePoint_to_UTF16
+
+// =================================================================================================
+
+static void Test_CodePoint_from_UTF16 ( FILE * log )
+{
+ UTF32Unit i, j;
+ size_t len;
+ UTF32Unit cp, cp0, cpx;
+ UTF16Unit u16[3];
+
+ // ------------------------------------------
+ // Test CodePoint_from_UTF16BE on good input.
+
+ fprintf ( log, "\nTesting CodePoint_from_UTF16BE on good input\n" );
+
+ // Some explicit sanity tests, in case the code and exhaustive tests have inverse bugs.
+ if ( kBigEndianHost ) {
+ u16[0] = 0x1234;
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0x1234) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+1234\n" );
+ u16[0] = 0xFEDC;
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0xFEDC) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+FEDC\n" );
+ u16[0] = 0xD812; u16[1] = 0xDC34;
+ CodePoint_from_UTF16BE ( u16, 2, &cp, &len );
+ if ( (len != 2) || (cp != 0x14834) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+14834\n" );
+ } else {
+ u16[0] = 0x3412;
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0x1234) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+1234\n" );
+ u16[0] = 0xDCFE;
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0xFEDC) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+FEDC\n" );
+ u16[0] = 0x12D8; u16[1] = 0x34DC;
+ CodePoint_from_UTF16BE ( u16, 2, &cp, &len );
+ if ( (len != 2) || (cp != 0x14834) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+14834\n" );
+ }
+ fprintf ( log, " CodePoint_from_UTF16BE sanity tests done\n" );
+
+ // Test the low part of the BMP, 0000..D7FF.
+ cp0 = 0;
+ for ( i = 0; i < 0xD800; ++i ) {
+ u16[0] = NativeUTF16BE(UTF16Unit(i)); u16[1] = 0xFFFF; cpx = i;
+ CodePoint_from_UTF16BE ( u16, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ }
+ fprintf ( log, " CodePoint_from_UTF16BE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test the high part of the BMP, E000..FFFF.
+ cp0 = 0xE000;
+ for ( i = cp0; i < 0x10000; ++i ) {
+ u16[0] = NativeUTF16BE(UTF16Unit(i)); u16[1] = 0xFFFF; cpx = i;
+ CodePoint_from_UTF16BE ( u16, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ }
+ fprintf ( log, " CodePoint_from_UTF16BE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test beyond the BMP, 10000..10FFFF.
+ cp0 = 0x10000;
+ for ( i = 0; i < 0x400; ++i ) {
+ for ( j = 0; j < 0x400; ++j ) {
+ cpx = (i<<10) + j + cp0;
+ u16[0] = NativeUTF16BE(0xD800+UTF16Unit(i)); u16[1] = NativeUTF16BE(0xDC00+UTF16Unit(j)); u16[2] = 0xFFFF;
+ CodePoint_from_UTF16BE ( u16, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 1, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 2) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16BE ( u16, 3, &cp, &len );
+ if ( (cp != cpx) || (len != 2) ) fprintf ( log, " *** CodePoint_from_UTF16BE failure for U+%.4X\n", cpx );
+ }
+ }
+ fprintf ( log, " CodePoint_from_UTF16BE done for %.4X..%.4X\n", cp0, cpx );
+
+ // ------------------------------------------
+ // Test CodePoint_from_UTF16LE on good input.
+
+ fprintf ( log, "\nTesting CodePoint_from_UTF16LE on good input\n" );
+
+ // Some explicit sanity tests, in case the code and exhaustive tests have inverse bugs.
+ if ( kBigEndianHost ) {
+ u16[0] = 0x3412;
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0x1234) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+1234\n" );
+ u16[0] = 0xDCFE;
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0xFEDC) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+FEDC\n" );
+ u16[0] = 0x12D8; u16[1] = 0x34DC;
+ CodePoint_from_UTF16LE ( u16, 2, &cp, &len );
+ if ( (len != 2) || (cp != 0x14834) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+14834\n" );
+ } else {
+ u16[0] = 0x1234;
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0x1234) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+1234\n" );
+ u16[0] = 0xFEDC;
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( (len != 1) || (cp != 0xFEDC) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+FEDC\n" );
+ u16[0] = 0xD812; u16[1] = 0xDC34;
+ CodePoint_from_UTF16LE ( u16, 2, &cp, &len );
+ if ( (len != 2) || (cp != 0x14834) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+14834\n" );
+ }
+ fprintf ( log, " CodePoint_from_UTF16LE sanity tests done\n" );
+
+ // Test the low part of the BMP, 0000..D7FF.
+ cp0 = 0;
+ for ( i = 0; i < 0xD800; ++i ) {
+ u16[0] = NativeUTF16LE(UTF16Unit(i)); u16[1] = 0xFFFF; cpx = i;
+ CodePoint_from_UTF16LE ( u16, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ }
+ fprintf ( log, " CodePoint_from_UTF16LE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test the high part of the BMP, E000..FFFF.
+ cp0 = 0xE000;
+ for ( i = cp0; i < 0x10000; ++i ) {
+ u16[0] = NativeUTF16LE(UTF16Unit(i)); u16[1] = 0xFFFF; cpx = i;
+ CodePoint_from_UTF16LE ( u16, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 1) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ }
+ fprintf ( log, " CodePoint_from_UTF16LE done for %.4X..%.4X\n", cp0, cpx );
+
+ // Test beyond the BMP, 10000..10FFFF.
+ cp0 = 0x10000;
+ for ( i = 0; i < 0x400; ++i ) {
+ for ( j = 0; j < 0x400; ++j ) {
+ cpx = (i<<10) + j + cp0;
+ u16[0] = NativeUTF16LE(0xD800+UTF16Unit(i)); u16[1] = NativeUTF16LE(0xDC00+UTF16Unit(j)); u16[2] = 0xFFFF;
+ CodePoint_from_UTF16LE ( u16, 0, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 1, &cp, &len );
+ if ( len != 0 ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 2, &cp, &len );
+ if ( (cp != cpx) || (len != 2) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ CodePoint_from_UTF16LE ( u16, 3, &cp, &len );
+ if ( (cp != cpx) || (len != 2) ) fprintf ( log, " *** CodePoint_from_UTF16LE failure for U+%.4X\n", cpx );
+ }
+ }
+ fprintf ( log, " CodePoint_from_UTF16LE done for %.4X..%.4X\n", cp0, cpx );
+
+ // ---------------------------------------------------------------
+ // Test CodePoint_from_UTF16 with bad input. U+12345 is D808 DF45.
+
+ fprintf ( log, "\nTesting CodePoint_from_UTF16 with bad input\n" );
+
+ memcpy ( sU16, "\xD8\x08\x00\x20\x00\x00", 6 ); // ! HPPA (maybe others) won't tolerate misaligned loads.
+ Bad_CodePoint_from_UTF16BE ( log, sU16, 3, "missing low surrogate" );
+ memcpy ( sU16, "\xDF\x45\x00\x20\x00\x00", 6 );
+ Bad_CodePoint_from_UTF16BE ( log, sU16, 3, "leading low surrogate" );
+ memcpy ( sU16, "\xD8\x08\xD8\x08\x00\x20\x00\x00", 8 );
+ Bad_CodePoint_from_UTF16BE ( log, sU16, 4, "double high surrogate" );
+
+ fprintf ( log, " CodePoint_from_UTF16BE done with bad input\n" );
+
+ memcpy ( sU16, "\x08\xD8\x20\x00\x00\x00", 6 );
+ Bad_CodePoint_from_UTF16LE ( log, sU16, 3, "missing low surrogate" );
+ memcpy ( sU16, "\x45\xDF\x20\x00\x00\x00", 6 );
+ Bad_CodePoint_from_UTF16LE ( log, sU16, 3, "leading low surrogate" );
+ memcpy ( sU16, "\x08\xD8\x08\xD8\x20\x00\x00\x00", 8 );
+ Bad_CodePoint_from_UTF16LE ( log, sU16, 4, "double high surrogate" );
+
+ fprintf ( log, " CodePoint_from_UTF16LE done with bad input\n" );
+
+} // Test_CodePoint_from_UTF16
+
+// =================================================================================================
+
+static void Test_UTF8_to_UTF16 ( FILE * log )
+{
+ size_t i;
+ size_t len8, len16, len8x, len16x;
+ UTF32Unit cp, cpx, cpLo, cpHi;
+
+ // ---------------------------------------------------------------------------------------
+ // Test UTF8_to_UTF16BE on good input. The CodePoint to/from functions are already tested,
+ // use them to verify the results here.
+
+ fprintf ( log, "\nTesting UTF8_to_UTF16BE on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len8 = len16 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU8[i] = UTF8Unit(cp);
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16BE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16BE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x80; cpHi = 0xD800; len16 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != (2*(0x800-cpLo) + 3*(cpHi-0x800)) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16BE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16BE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != 3*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16BE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16BE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len16 = (cpHi-cpLo)*2;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != 4*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16BE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16BE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len16 = 0x80*(1+1+1+2);
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_to_UTF8 ( i, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x100, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x1000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x10000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16BE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16BE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, len16 = 0; i < 0x80; ++i ) {
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", i );
+ len16 += len16x;
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i+0x100) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", i+0x100 );
+ len16 += len16x;
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i+0x1000) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", i+0x1000 );
+ len16 += len16x;
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF8_to_UTF16BE failure for U+%.4X\n", i+0x10000 );
+ len16 += len16x;
+ }
+ if ( len16 != 0x80*(1+1+1+2) ) fprintf ( log, " *** UTF8_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16BE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len8 = 0x80*(1+2+3+4); len16 = 0x80*(1+1+1+2);
+
+ UTF8_to_UTF16BE ( sU8, 0, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF8_to_UTF16BE empty input failure, %d -> %d\n", len8x, len16x );
+ UTF8_to_UTF16BE ( sU8, len8, sU16, 0, &len8x, &len16x );
+ if ( (len8x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF8_to_UTF16BE empty output failure, %d -> %d\n", len8x, len16x );
+ UTF8_to_UTF16BE ( sU8, 8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8x != 6) || (len16x != 3) ) fprintf ( log, " *** UTF8_to_UTF16BE partial input failure, %d -> %d\n", len8x, len16x );
+ UTF8_to_UTF16BE ( sU8, len8, sU16, 4, &len8x, &len16x );
+ if ( (len8x != 6) || (len16x != 3) ) fprintf ( log, " *** UTF8_to_UTF16BE partial output failure, %d -> %d\n", len8x, len16x );
+
+ fprintf ( log, " UTF8_to_UTF16BE done for empty buffers and buffers ending in mid character\n" );
+
+ // -----------------------------------
+ // Test UTF8_to_UTF16LE on good input.
+
+ fprintf ( log, "\nTesting UTF8_to_UTF16LE on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len8 = len16 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU8[i] = UTF8Unit(cp);
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16LE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16LE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x80; cpHi = 0xD800; len16 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != (2*(0x800-cpLo) + 3*(cpHi-0x800)) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16LE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16LE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != 3*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16LE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16LE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len16 = (cpHi-cpLo)*2;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != 4*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16LE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16LE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF8_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len16 = 0x80*(1+1+1+2);
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_to_UTF8 ( i, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x100, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x1000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x10000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF16LE ( sU8, len8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8 != len8x) || (len16 != len16x) ) fprintf ( log, " *** UTF8_to_UTF16LE length failure, %d -> %d\n", len8x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, len16 = 0; i < 0x80; ++i ) {
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", i );
+ len16 += len16x;
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i+0x100) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", i+0x100 );
+ len16 += len16x;
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i+0x1000) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", i+0x1000 );
+ len16 += len16x;
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF8_to_UTF16LE failure for U+%.4X\n", i+0x10000 );
+ len16 += len16x;
+ }
+ if ( len16 != 0x80*(1+1+1+2) ) fprintf ( log, " *** UTF8_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF8_to_UTF16LE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len8 = 0x80*(1+2+3+4); len16 = 0x80*(1+1+1+2);
+
+ UTF8_to_UTF16LE ( sU8, 0, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF8_to_UTF16LE empty input failure, %d -> %d\n", len8x, len16x );
+ UTF8_to_UTF16LE ( sU8, len8, sU16, 0, &len8x, &len16x );
+ if ( (len8x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF8_to_UTF16LE empty output failure, %d -> %d\n", len8x, len16x );
+ UTF8_to_UTF16LE ( sU8, 8, sU16, sizeof(sU16), &len8x, &len16x );
+ if ( (len8x != 6) || (len16x != 3) ) fprintf ( log, " *** UTF8_to_UTF16LE partial input failure, %d -> %d\n", len8x, len16x );
+ UTF8_to_UTF16LE ( sU8, len8, sU16, 4, &len8x, &len16x );
+ if ( (len8x != 6) || (len16x != 3) ) fprintf ( log, " *** UTF8_to_UTF16LE partial output failure, %d -> %d\n", len8x, len16x );
+
+ fprintf ( log, " UTF8_to_UTF16LE done for empty buffers and buffers ending in mid character\n" );
+
+} // Test_UTF8_to_UTF16
+
+// =================================================================================================
+
+static void Test_UTF8_to_UTF32 ( FILE * log )
+{
+ size_t i;
+ size_t len8, len32, len8x, len32x;
+ UTF32Unit cp, cpLo, cpHi;
+
+ // ---------------------------------------------------------------------------------------
+ // Test UTF8_to_UTF32BE on good input. The CodePoint to/from functions are already tested,
+ // use them to verify the results here.
+
+ fprintf ( log, "\nTesting UTF8_to_UTF32BE on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len8 = len32 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU8[i] = UTF8Unit(cp);
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32BE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32BE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x80; cpHi = 0xD800; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != (2*(0x800-cpLo) + 3*(cpHi-0x800)) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32BE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32BE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 !=3*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32BE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32BE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 !=4*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32BE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32BE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len32 = 0x80*(1+1+1+1);
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_to_UTF8 ( i, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x100, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x1000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x10000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32BE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32BE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, len32 = 0; i < 0x80; ++i ) {
+ if ( sU32[len32] != NativeUTF32BE(i) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", i );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32BE(i+0x100) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", i+0x100 );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32BE(i+0x1000) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", i+0x1000 );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32BE(i+0x10000) ) fprintf ( log, " *** UTF8_to_UTF32BE failure for U+%.4X\n", i+0x10000 );
+ ++len32;
+ }
+
+ fprintf ( log, " UTF8_to_UTF32BE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len8 = 0x80*(1+2+3+4); len32 = 0x80*(1+1+1+1);
+
+ UTF8_to_UTF32BE ( sU8, 0, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF8_to_UTF32BE empty input failure, %d -> %d\n", len8x, len32x );
+ UTF8_to_UTF32BE ( sU8, len8, sU32, 0, &len8x, &len32x );
+ if ( (len8x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF8_to_UTF32BE empty output failure, %d -> %d\n", len8x, len32x );
+ UTF8_to_UTF32BE ( sU8, 8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8x != 6) || (len32x != 3) ) fprintf ( log, " *** UTF8_to_UTF32BE partial input failure, %d -> %d\n", len8x, len32x );
+
+ fprintf ( log, " UTF8_to_UTF32BE done for empty buffers and buffers ending in mid character\n" );
+
+ // -----------------------------------
+ // Test UTF8_to_UTF32LE on good input.
+
+ fprintf ( log, "\nTesting UTF8_to_UTF32LE on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len8 = len32 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU8[i] = UTF8Unit(cp);
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32LE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32LE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x80; cpHi = 0xD800; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 != (2*(0x800-cpLo) + 3*(cpHi-0x800)) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32LE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32LE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 !=3*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32LE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32LE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len8 = 0; cp < cpHi; ++cp, len8 += len8x ) CodePoint_to_UTF8 ( cp, &sU8[len8], 8, &len8x );
+ if ( len8 !=4*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32LE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32LE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF8_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len32 = 0x80*(1+1+1+1);
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_to_UTF8 ( i, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x100, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x1000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ CodePoint_to_UTF8 ( i+0x10000, &sU8[len8], 8, &len8x );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** CodePoint_to_UTF8 length failure, %d\n", len8 );
+ sU8[len8] = 0xFF;
+
+ UTF8_to_UTF32LE ( sU8, len8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8 != len8x) || (len32 != len32x) ) fprintf ( log, " *** UTF8_to_UTF32LE length failure, %d -> %d\n", len8x, len32x );
+
+ sU32[len32x] = 0xFFFFFFFF;
+ for ( i = 0, len32 = 0; i < 0x80; ++i ) {
+ if ( sU32[len32] != NativeUTF32LE(i) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", i );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32LE(i+0x100) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", i+0x100 );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32LE(i+0x1000) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", i+0x1000 );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32LE(i+0x10000) ) fprintf ( log, " *** UTF8_to_UTF32LE failure for U+%.4X\n", i+0x10000 );
+ ++len32;
+ }
+
+ fprintf ( log, " UTF8_to_UTF32LE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len8 = 0x80*(1+2+3+4); len32 = 0x80*(1+1+1+1);
+
+ UTF8_to_UTF32LE ( sU8, 0, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF8_to_UTF32LE empty input failure, %d -> %d\n", len8x, len32x );
+ UTF8_to_UTF32LE ( sU8, len8, sU32, 0, &len8x, &len32x );
+ if ( (len8x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF8_to_UTF32LE empty output failure, %d -> %d\n", len8x, len32x );
+ UTF8_to_UTF32LE ( sU8, 8, sU32, sizeof(sU32), &len8x, &len32x );
+ if ( (len8x != 6) || (len32x != 3) ) fprintf ( log, " *** UTF8_to_UTF32LE partial input failure, %d -> %d\n", len8x, len32x );
+
+ fprintf ( log, " UTF8_to_UTF32LE done for empty buffers and buffers ending in mid character\n" );
+
+} // Test_UTF8_to_UTF32
+
+// =================================================================================================
+
+static void Test_UTF16_to_UTF8 ( FILE * log )
+{
+ size_t i;
+ size_t len16, len8, len16x, len8x;
+ UTF32Unit cp, cpx, cpLo, cpHi;
+
+ // ---------------------------------------------------------------------------------------
+ // Test UTF16BE_to_UTF8 on good input. The CodePoint to/from functions are already tested,
+ // use them to verify the results here.
+
+ fprintf ( log, "\nTesting UTF16BE_to_UTF8 on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len16 = len8 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16BE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 2 byte non-ASCII inside the BMP.
+
+ cpLo = 0x80; cpHi = 0x800; len16 = cpHi-cpLo; len8 = 2*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16BE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 3 byte non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x800; cpHi = 0xD800; len16 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16BE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16BE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len8 = (cpHi-cpLo)*4;
+ for ( cp = cpLo, len16 = 0; cp < cpHi; ++cp, len16 += len16x ) CodePoint_to_UTF16BE ( cp, &sU16[len16], 4, &len16x );
+ if ( len16 != 2*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF16BE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16BE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != cp) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len8 = 0x80*(1+2+3+4);
+ for ( i = 0, len16 = 0; i < 0x80; ++i ) {
+ CodePoint_to_UTF16BE ( i, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16BE ( i+0x100, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16BE ( i+0x1000, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16BE ( i+0x10000, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ }
+ if ( len16 != 0x80*(1+1+1+2) ) fprintf ( log, " *** CodePoint_to_UTF16BE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16BE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != i) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", i );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != i+0x100) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", i+0x100 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != i+0x1000) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", i+0x1000 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF16BE_to_UTF8 failure for U+%.4X\n", i+0x10000 );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** UTF16BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len16 = 0x80*(1+1+1+2); len8 = 0x80*(1+2+3+4);
+
+ UTF16BE_to_UTF8 ( sU16, 0, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF16BE_to_UTF8 empty input failure, %d -> %d\n", len16x, len8x );
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, 0, &len16x, &len8x );
+ if ( (len16x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF16BE_to_UTF8 empty output failure, %d -> %d\n", len16x, len8x );
+ UTF16BE_to_UTF8 ( sU16, 4, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16x != 3) || (len8x != 6) ) fprintf ( log, " *** UTF16BE_to_UTF8 partial input failure, %d -> %d\n", len16x, len8x );
+ UTF16BE_to_UTF8 ( sU16, len16, sU8, 8, &len16x, &len8x );
+ if ( (len16x != 3) || (len8x != 6) ) fprintf ( log, " *** UTF16BE_to_UTF8 partial output failure, %d -> %d\n", len16x, len8x );
+
+ fprintf ( log, " UTF16BE_to_UTF8 done for empty buffers and buffers ending in mid character\n" );
+
+ // -----------------------------------
+ // Test UTF16LE_to_UTF8 on good input.
+
+ fprintf ( log, "\nTesting UTF16LE_to_UTF8 on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len16 = len8 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16LE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 2 byte non-ASCII inside the BMP.
+
+ cpLo = 0x80; cpHi = 0x800; len16 = cpHi-cpLo; len8 = 2*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16LE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 3 byte non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x800; cpHi = 0xD800; len16 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16LE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16LE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len8 = (cpHi-cpLo)*4;
+ for ( cp = cpLo, len16 = 0; cp < cpHi; ++cp, len16 += len16x ) CodePoint_to_UTF16LE ( cp, &sU16[len16], 4, &len16x );
+ if ( len16 != 2*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF16LE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16LE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != cp) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF16LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len8 = 0x80*(1+2+3+4);
+ for ( i = 0, len16 = 0; i < 0x80; ++i ) {
+ CodePoint_to_UTF16LE ( i, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16LE ( i+0x100, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16LE ( i+0x1000, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16LE ( i+0x10000, &sU16[len16], 4, &len16x );
+ len16 += len16x;
+ }
+ if ( len16 != 0x80*(1+1+1+2) ) fprintf ( log, " *** CodePoint_to_UTF16LE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16 != len16x) || (len8 != len8x) ) fprintf ( log, " *** UTF16LE_to_UTF8 length failure, %d -> %d\n", len16x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != i) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", i );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != i+0x100) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", i+0x100 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != i+0x1000) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", i+0x1000 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF16LE_to_UTF8 failure for U+%.4X\n", i+0x10000 );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** UTF16LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len16 = 0x80*(1+1+1+2); len8 = 0x80*(1+2+3+4);
+
+ UTF16LE_to_UTF8 ( sU16, 0, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF16LE_to_UTF8 empty input failure, %d -> %d\n", len16x, len8x );
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, 0, &len16x, &len8x );
+ if ( (len16x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF16LE_to_UTF8 empty output failure, %d -> %d\n", len16x, len8x );
+ UTF16LE_to_UTF8 ( sU16, 4, sU8, sizeof(sU8), &len16x, &len8x );
+ if ( (len16x != 3) || (len8x != 6) ) fprintf ( log, " *** UTF16LE_to_UTF8 partial input failure, %d -> %d\n", len16x, len8x );
+ UTF16LE_to_UTF8 ( sU16, len16, sU8, 8, &len16x, &len8x );
+ if ( (len16x != 3) || (len8x != 6) ) fprintf ( log, " *** UTF16LE_to_UTF8 partial output failure, %d -> %d\n", len16x, len8x );
+
+ fprintf ( log, " UTF16LE_to_UTF8 done for empty buffers and buffers ending in mid character\n" );
+
+} // Test_UTF16_to_UTF8
+
+// =================================================================================================
+
+static void Test_UTF32_to_UTF8 ( FILE * log )
+{
+ size_t i;
+ size_t len32, len8, len32x, len8x;
+ UTF32Unit cp, cpx, cpLo, cpHi;
+
+ // -----------------------------------
+ // Test UTF32BE_to_UTF8 on good input.
+
+ fprintf ( log, "\nTesting UTF32BE_to_UTF8 on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len32 = len8 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32BE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 2 byte non-ASCII inside the BMP.
+
+ cpLo = 0x80; cpHi = 0x800; len32 = cpHi-cpLo; len8 = 2*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32BE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 3 byte non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x800; cpHi = 0xD800; len32 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32BE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32BE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo; len8 = (cpHi-cpLo)*4;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32BE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len8 = 0x80*(1+2+3+4);
+ for ( i = 0, len32 = 0; i < 0x80; ++i ) {
+ sU32[len32] = NativeUTF32BE(i);
+ ++len32;
+ sU32[len32] = NativeUTF32BE(i+0x100);
+ ++len32;
+ sU32[len32] = NativeUTF32BE(i+0x1000);
+ ++len32;
+ sU32[len32] = NativeUTF32BE(i+0x10000);
+ ++len32;
+ }
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32BE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != i) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", i );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != i+0x100) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", i+0x100 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != i+0x1000) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", i+0x1000 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF32BE_to_UTF8 failure for U+%.4X\n", i+0x10000 );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** UTF32BE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len32 = 0x80*(1+1+1+2); len8 = 0x80*(1+2+3+4);
+
+ UTF32BE_to_UTF8 ( sU32, 0, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF32BE_to_UTF8 empty input failure, %d -> %d\n", len32x, len8x );
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, 0, &len32x, &len8x );
+ if ( (len32x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF32BE_to_UTF8 empty output failure, %d -> %d\n", len32x, len8x );
+ UTF32BE_to_UTF8 ( sU32, len32, sU8, 8, &len32x, &len8x );
+ if ( (len32x != 3) || (len8x != 6) ) fprintf ( log, " *** UTF32BE_to_UTF8 partial output failure, %d -> %d\n", len32x, len8x );
+
+ fprintf ( log, " UTF32BE_to_UTF8 done for empty buffers and buffers ending in mid character\n" );
+
+ // -----------------------------------
+ // Test UTF32LE_to_UTF8 on good input.
+
+ fprintf ( log, "\nTesting UTF32LE_to_UTF8 on good input\n" );
+
+ // Test ASCII.
+
+ cpLo = 0; cpHi = 0x80; len32 = len8 = 0x80;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32LE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 2 byte non-ASCII inside the BMP.
+
+ cpLo = 0x80; cpHi = 0x800; len32 = cpHi-cpLo; len8 = 2*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32LE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test 3 byte non-ASCII inside the BMP, below the surrogates.
+
+ cpLo = 0x800; cpHi = 0xD800; len32 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32LE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = cpHi-cpLo; len8 = 3*(cpHi-cpLo);
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32LE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo; len8 = (cpHi-cpLo)*4;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32LE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len8x, ++cp ) {
+ CodePoint_from_UTF8 ( &sU8[i], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", cp );
+ }
+ if ( i != len8 ) fprintf ( log, " *** UTF32LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating ASCII, non-ASCII BMP, beyond BMP.
+
+ len8 = 0x80*(1+2+3+4);
+ for ( i = 0, len32 = 0; i < 0x80; ++i ) {
+ sU32[len32] = NativeUTF32LE(i);
+ ++len32;
+ sU32[len32] = NativeUTF32LE(i+0x100);
+ ++len32;
+ sU32[len32] = NativeUTF32LE(i+0x1000);
+ ++len32;
+ sU32[len32] = NativeUTF32LE(i+0x10000);
+ ++len32;
+ }
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32 != len32x) || (len8 != len8x) ) fprintf ( log, " *** UTF32LE_to_UTF8 length failure, %d -> %d\n", len32x, len8x );
+
+ sU8[len8] = 0xFF;
+ for ( i = 0, len8 = 0; i < 0x80; ++i ) {
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 1) || (cpx != i) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", i );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 2) || (cpx != i+0x100) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", i+0x100 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 3) || (cpx != i+0x1000) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", i+0x1000 );
+ len8 += len8x;
+ CodePoint_from_UTF8 ( &sU8[len8], 8, &cpx, &len8x );
+ if ( (len8x != 4) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF32LE_to_UTF8 failure for U+%.4X\n", i+0x10000 );
+ len8 += len8x;
+ }
+ if ( len8 != 0x80*(1+2+3+4) ) fprintf ( log, " *** UTF32LE_to_UTF8 consume failure, %d != %d\n", i, len8 );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len32 = 0x80*(1+1+1+2); len8 = 0x80*(1+2+3+4);
+
+ UTF32LE_to_UTF8 ( sU32, 0, sU8, sizeof(sU8), &len32x, &len8x );
+ if ( (len32x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF32LE_to_UTF8 empty input failure, %d -> %d\n", len32x, len8x );
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, 0, &len32x, &len8x );
+ if ( (len32x != 0) || (len8x != 0) ) fprintf ( log, " *** UTF32LE_to_UTF8 empty output failure, %d -> %d\n", len32x, len8x );
+ UTF32LE_to_UTF8 ( sU32, len32, sU8, 8, &len32x, &len8x );
+ if ( (len32x != 3) || (len8x != 6) ) fprintf ( log, " *** UTF32LE_to_UTF8 partial output failure, %d -> %d\n", len32x, len8x );
+
+ fprintf ( log, " UTF32LE_to_UTF8 done for empty buffers and buffers ending in mid character\n" );
+
+} // Test_UTF32_to_UTF8
+
+// =================================================================================================
+
+static void Test_UTF16_to_UTF32 ( FILE * log )
+{
+ size_t i;
+ size_t len16, len32, len16x, len32x;
+ UTF32Unit cp, cpLo, cpHi;
+
+ // --------------------------------------
+ // Test UTF16BE_to_UTF32BE on good input.
+
+ fprintf ( log, "\nTesting UTF16BE_to_UTF32BE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF16BE_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF16BE_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len16 = 0; cp < cpHi; ++cp, len16 += len16x ) CodePoint_to_UTF16BE ( cp, &sU16[len16], 4, &len16x );
+ if ( len16 != 2*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF16BE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF16BE_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_to_UTF16BE ( i, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16BE ( i+0x10000, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** CodePoint_to_UTF16BE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ if ( sU32[len32] != NativeUTF32BE(i) ) fprintf ( log, " *** UTF16BE_to_UTF32BE failure for U+%.4X\n", i );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32BE(i+0x10000) ) fprintf ( log, " *** UTF16BE_to_UTF32BE failure for U+%.4X\n", i+0x10000 );
+ ++len32;
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32BE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+
+ UTF16BE_to_UTF32BE ( sU16, 0, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16BE_to_UTF32BE empty input failure, %d -> %d\n", len16x, len32x );
+ UTF16BE_to_UTF32BE ( sU16, len16, sU32, 0, &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16BE_to_UTF32BE empty output failure, %d -> %d\n", len16x, len32x );
+ UTF16BE_to_UTF32BE ( sU16, 5, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 4) || (len32x != 3) ) fprintf ( log, " *** UTF16BE_to_UTF32BE partial input failure, %d -> %d\n", len16x, len32x );
+
+ fprintf ( log, " UTF16BE_to_UTF32BE done for empty buffers and buffers ending in mid character\n" );
+
+ // --------------------------------------
+ // Test UTF16LE_to_UTF32LE on good input.
+
+ fprintf ( log, "\nTesting UTF16LE_to_UTF32LE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF16LE_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF16LE_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len16 = 0; cp < cpHi; ++cp, len16 += len16x ) CodePoint_to_UTF16LE ( cp, &sU16[len16], 4, &len16x );
+ if ( len16 != 2*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF16LE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF16LE_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_to_UTF16LE ( i, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16LE ( i+0x10000, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** CodePoint_to_UTF16LE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ if ( sU32[len32] != NativeUTF32LE(i) ) fprintf ( log, " *** UTF16LE_to_UTF32LE failure for U+%.4X\n", i );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32LE(i+0x10000) ) fprintf ( log, " *** UTF16LE_to_UTF32LE failure for U+%.4X\n", i+0x10000 );
+ ++len32;
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32LE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+
+ UTF16LE_to_UTF32LE ( sU16, 0, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16LE_to_UTF32LE empty input failure, %d -> %d\n", len16x, len32x );
+ UTF16LE_to_UTF32LE ( sU16, len16, sU32, 0, &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16LE_to_UTF32LE empty output failure, %d -> %d\n", len16x, len32x );
+ UTF16LE_to_UTF32LE ( sU16, 5, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 4) || (len32x != 3) ) fprintf ( log, " *** UTF16LE_to_UTF32LE partial input failure, %d -> %d\n", len16x, len32x );
+
+ fprintf ( log, " UTF16LE_to_UTF32LE done for empty buffers and buffers ending in mid character\n" );
+
+ // --------------------------------------
+ // Test UTF16BE_to_UTF32LE on good input.
+
+ fprintf ( log, "\nTesting UTF16BE_to_UTF32LE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF16BE_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16BE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF16BE_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len16 = 0; cp < cpHi; ++cp, len16 += len16x ) CodePoint_to_UTF16BE ( cp, &sU16[len16], 4, &len16x );
+ if ( len16 != 2*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF16BE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32LE(cp) ) fprintf ( log, " *** UTF16BE_to_UTF32LE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_to_UTF16BE ( i, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16BE ( i+0x10000, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** CodePoint_to_UTF16BE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+
+ UTF16BE_to_UTF32LE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16BE_to_UTF32LE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ if ( sU32[len32] != NativeUTF32LE(i) ) fprintf ( log, " *** UTF16BE_to_UTF32LE failure for U+%.4X\n", i );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32LE(i+0x10000) ) fprintf ( log, " *** UTF16BE_to_UTF32LE failure for U+%.4X\n", i+0x10000 );
+ ++len32;
+ }
+
+ fprintf ( log, " UTF16BE_to_UTF32LE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+
+ UTF16BE_to_UTF32LE ( sU16, 0, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16BE_to_UTF32LE empty input failure, %d -> %d\n", len16x, len32x );
+ UTF16BE_to_UTF32LE ( sU16, len16, sU32, 0, &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16BE_to_UTF32LE empty output failure, %d -> %d\n", len16x, len32x );
+ UTF16BE_to_UTF32LE ( sU16, 5, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 4) || (len32x != 3) ) fprintf ( log, " *** UTF16BE_to_UTF32LE partial input failure, %d -> %d\n", len16x, len32x );
+
+ fprintf ( log, " UTF16BE_to_UTF32LE done for empty buffers and buffers ending in mid character\n" );
+
+ // --------------------------------------
+ // Test UTF16LE_to_UTF32BE on good input.
+
+ fprintf ( log, "\nTesting UTF16LE_to_UTF32BE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF16LE_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len16 = len32 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU16[i] = NativeUTF16LE(UTF16Unit(cp));
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF16LE_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo;
+ for ( cp = cpLo, len16 = 0; cp < cpHi; ++cp, len16 += len16x ) CodePoint_to_UTF16LE ( cp, &sU16[len16], 4, &len16x );
+ if ( len16 != 2*(cpHi-cpLo) ) fprintf ( log, " *** CodePoint_to_UTF16LE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) {
+ if ( sU32[i] != NativeUTF32BE(cp) ) fprintf ( log, " *** UTF16LE_to_UTF32BE failure for U+%.4X\n", cp );
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_to_UTF16LE ( i, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ CodePoint_to_UTF16LE ( i+0x10000, &sU16[len16], 8, &len16x );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** CodePoint_to_UTF16LE length failure, %d\n", len16 );
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+
+ UTF16LE_to_UTF32BE ( sU16, len16, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16 != len16x) || (len32 != len32x) ) fprintf ( log, " *** UTF16LE_to_UTF32BE length failure, %d -> %d\n", len16x, len32x );
+
+ sU32[len32] = 0xFFFFFFFF;
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ if ( sU32[len32] != NativeUTF32BE(i) ) fprintf ( log, " *** UTF16LE_to_UTF32BE failure for U+%.4X\n", i );
+ ++len32;
+ if ( sU32[len32] != NativeUTF32BE(i+0x10000) ) fprintf ( log, " *** UTF16LE_to_UTF32BE failure for U+%.4X\n", i+0x10000 );
+ ++len32;
+ }
+
+ fprintf ( log, " UTF16LE_to_UTF32BE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len16 = 0x8000*(1+2); len32 = 0x8000*(1+1);
+
+ UTF16LE_to_UTF32BE ( sU16, 0, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16LE_to_UTF32BE empty input failure, %d -> %d\n", len16x, len32x );
+ UTF16LE_to_UTF32BE ( sU16, len16, sU32, 0, &len16x, &len32x );
+ if ( (len16x != 0) || (len32x != 0) ) fprintf ( log, " *** UTF16LE_to_UTF32BE empty output failure, %d -> %d\n", len16x, len32x );
+ UTF16LE_to_UTF32BE ( sU16, 5, sU32, sizeof(sU32), &len16x, &len32x );
+ if ( (len16x != 4) || (len32x != 3) ) fprintf ( log, " *** UTF16LE_to_UTF32BE partial input failure, %d -> %d\n", len16x, len32x );
+
+ fprintf ( log, " UTF16LE_to_UTF32BE done for empty buffers and buffers ending in mid character\n" );
+
+} // Test_UTF16_to_UTF32
+
+// =================================================================================================
+
+static void Test_UTF32_to_UTF16 ( FILE * log )
+{
+ size_t i;
+ size_t len32, len16, len32x, len16x;
+ UTF32Unit cp, cpx, cpLo, cpHi;
+
+ // --------------------------------------
+ // Test UTF32BE_to_UTF16BE on good input.
+
+ fprintf ( log, "\nTesting UTF32BE_to_UTF16BE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32BE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32BE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo; len16 = (cpHi-cpLo)*2;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32BE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ sU32[len32] = NativeUTF32BE(i);
+ ++len32;
+ sU32[len32] = NativeUTF32BE(i+0x10000);
+ ++len32;
+ }
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i) ) fprintf ( log, " *** UTF32BE_to_UTF16BE failure for U+%.4X\n", i );
+ len16 += len16x;
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF32BE_to_UTF16BE failure for U+%.4X\n", i );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** UTF32BE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16BE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+
+ UTF32BE_to_UTF16BE ( sU32, 0, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32BE_to_UTF16BE empty input failure, %d -> %d\n", len32x, len16x );
+ UTF32BE_to_UTF16BE ( sU32, len32, sU16, 0, &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32BE_to_UTF16BE empty output failure, %d -> %d\n", len32x, len16x );
+ UTF32BE_to_UTF16BE ( sU32, len32, sU16, 5, &len32x, &len16x );
+ if ( (len32x != 3) || (len16x != 4) ) fprintf ( log, " *** UTF32BE_to_UTF16BE partial output failure, %d -> %d\n", len32x, len16x );
+
+ fprintf ( log, " UTF32BE_to_UTF16BE done for empty buffers and buffers ending in mid character\n" );
+
+// =================================================================================================
+
+ // --------------------------------------
+ // Test UTF32LE_to_UTF16LE on good input.
+
+ fprintf ( log, "\nTesting UTF32LE_to_UTF16LE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32LE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32LE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo; len16 = (cpHi-cpLo)*2;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32LE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ sU32[len32] = NativeUTF32LE(i);
+ ++len32;
+ sU32[len32] = NativeUTF32LE(i+0x10000);
+ ++len32;
+ }
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i) ) fprintf ( log, " *** UTF32LE_to_UTF16LE failure for U+%.4X\n", i );
+ len16 += len16x;
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF32LE_to_UTF16LE failure for U+%.4X\n", i );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** UTF32LE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16LE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+
+ UTF32LE_to_UTF16LE ( sU32, 0, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32LE_to_UTF16LE empty input failure, %d -> %d\n", len32x, len16x );
+ UTF32LE_to_UTF16LE ( sU32, len32, sU16, 0, &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32LE_to_UTF16LE empty output failure, %d -> %d\n", len32x, len16x );
+ UTF32LE_to_UTF16LE ( sU32, len32, sU16, 5, &len32x, &len16x );
+ if ( (len32x != 3) || (len16x != 4) ) fprintf ( log, " *** UTF32LE_to_UTF16LE partial output failure, %d -> %d\n", len32x, len16x );
+
+ fprintf ( log, " UTF32LE_to_UTF16LE done for empty buffers and buffers ending in mid character\n" );
+
+// =================================================================================================
+
+ // --------------------------------------
+ // Test UTF32BE_to_UTF16LE on good input.
+
+ fprintf ( log, "\nTesting UTF32BE_to_UTF16LE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32BE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32BE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo; len16 = (cpHi-cpLo)*2;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32BE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16LE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF32BE_to_UTF16LE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32BE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16LE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ sU32[len32] = NativeUTF32BE(i);
+ ++len32;
+ sU32[len32] = NativeUTF32BE(i+0x10000);
+ ++len32;
+ }
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32BE_to_UTF16LE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32BE_to_UTF16LE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16LE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i) ) fprintf ( log, " *** UTF32BE_to_UTF16LE failure for U+%.4X\n", i );
+ len16 += len16x;
+ CodePoint_from_UTF16LE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF32BE_to_UTF16LE failure for U+%.4X\n", i );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** UTF32BE_to_UTF16LE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32BE_to_UTF16LE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+
+ UTF32BE_to_UTF16LE ( sU32, 0, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32BE_to_UTF16LE empty input failure, %d -> %d\n", len32x, len16x );
+ UTF32BE_to_UTF16LE ( sU32, len32, sU16, 0, &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32BE_to_UTF16LE empty output failure, %d -> %d\n", len32x, len16x );
+ UTF32BE_to_UTF16LE ( sU32, len32, sU16, 5, &len32x, &len16x );
+ if ( (len32x != 3) || (len16x != 4) ) fprintf ( log, " *** UTF32BE_to_UTF16LE partial output failure, %d -> %d\n", len32x, len16x );
+
+ fprintf ( log, " UTF32BE_to_UTF16LE done for empty buffers and buffers ending in mid character\n" );
+
+// =================================================================================================
+
+ // --------------------------------------
+ // Test UTF32LE_to_UTF16BE on good input.
+
+ fprintf ( log, "\nTesting UTF32LE_to_UTF16BE on good input\n" );
+
+ // Test inside the BMP, below the surrogates.
+
+ cpLo = 0; cpHi = 0xD800; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32LE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test inside the BMP, above the surrogates.
+
+ cpLo = 0xE000; cpHi = 0x10000; len32 = len16 = cpHi-cpLo;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32LE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test outside the BMP.
+
+ cpLo = 0x10000; cpHi = 0x110000; len32 = cpHi-cpLo; len16 = (cpHi-cpLo)*2;
+ for ( i = 0, cp = cpLo; cp < cpHi; ++i, ++cp ) sU32[i] = NativeUTF32LE(cp);
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, cp = cpLo; cp < cpHi; i += len16x, ++cp ) {
+ CodePoint_from_UTF16BE ( &sU16[i], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != cp) ) fprintf ( log, " *** UTF32LE_to_UTF16BE failure for U+%.4X\n", cp );
+ }
+ if ( i != len16 ) fprintf ( log, " *** UTF32LE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16BE done for %.4X..%.4X\n", cpLo, cpHi-1 );
+
+ // Test alternating BMP, beyond BMP.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+ for ( i = 0, len32 = 0; i < 0x8000; ++i ) {
+ sU32[len32] = NativeUTF32LE(i);
+ ++len32;
+ sU32[len32] = NativeUTF32LE(i+0x10000);
+ ++len32;
+ }
+ sU32[len32] = 0xFFFFFFFF;
+
+ UTF32LE_to_UTF16BE ( sU32, len32, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32 != len32x) || (len16 != len16x) ) fprintf ( log, " *** UTF32LE_to_UTF16BE length failure, %d -> %d\n", len32x, len16x );
+
+ sU16[len16] = NativeUTF16BE(0xDC00); // Isolated low surrogate.
+ for ( i = 0, len16 = 0; i < 0x8000; ++i ) {
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 1) || (cpx != i) ) fprintf ( log, " *** UTF32LE_to_UTF16BE failure for U+%.4X\n", i );
+ len16 += len16x;
+ CodePoint_from_UTF16BE ( &sU16[len16], 4, &cpx, &len16x );
+ if ( (len16x != 2) || (cpx != i+0x10000) ) fprintf ( log, " *** UTF32LE_to_UTF16BE failure for U+%.4X\n", i );
+ len16 += len16x;
+ }
+ if ( len16 != 0x8000*(1+2) ) fprintf ( log, " *** UTF32LE_to_UTF16BE consume failure, %d != %d\n", i, len16 );
+
+ fprintf ( log, " UTF32LE_to_UTF16BE done for mixed values\n" );
+
+ // Test empty buffers and buffers ending in mid character.
+
+ len32 = 0x8000*(1+1); len16 = 0x8000*(1+2);
+
+ UTF32LE_to_UTF16BE ( sU32, 0, sU16, sizeof(sU16), &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32LE_to_UTF16BE empty input failure, %d -> %d\n", len32x, len16x );
+ UTF32LE_to_UTF16BE ( sU32, len32, sU16, 0, &len32x, &len16x );
+ if ( (len32x != 0) || (len16x != 0) ) fprintf ( log, " *** UTF32LE_to_UTF16BE empty output failure, %d -> %d\n", len32x, len16x );
+ UTF32LE_to_UTF16BE ( sU32, len32, sU16, 5, &len32x, &len16x );
+ if ( (len32x != 3) || (len16x != 4) ) fprintf ( log, " *** UTF32LE_to_UTF16BE partial output failure, %d -> %d\n", len32x, len16x );
+
+ fprintf ( log, " UTF32LE_to_UTF16BE done for empty buffers and buffers ending in mid character\n" );
+
+} // Test_UTF32_to_UTF16
+
+// =================================================================================================
+
+static void DoTest ( FILE * log )
+{
+ InitializeUnicodeConversions();
+
+ Test_SwappingPrimitives ( log );
+
+ Test_CodePoint_to_UTF8 ( log );
+ Test_CodePoint_from_UTF8 ( log );
+
+ Test_CodePoint_to_UTF16 ( log );
+ Test_CodePoint_from_UTF16 ( log );
+
+ Test_UTF8_to_UTF16 ( log );
+ Test_UTF8_to_UTF32 ( log );
+
+ Test_UTF16_to_UTF8 ( log );
+ Test_UTF32_to_UTF8 ( log );
+
+ Test_UTF16_to_UTF32 ( log );
+ Test_UTF32_to_UTF16 ( log );
+
+} // DoTest
+
+// =================================================================================================
+
+extern "C" int main ( void )
+{
+ char buffer [1000];
+
+ #if !XMP_AutomatedTestBuild
+ FILE * log = stdout;
+ #else
+ FILE * log = fopen ( "TestUnicode.out", "wb" );
+ #endif
+
+ time_t now;
+ time ( &now );
+ sprintf ( buffer, "// Starting test for Unicode conversion correctness, %s", ctime ( &now ) );
+
+ fprintf ( log, "// " );
+ for ( size_t i = 4; i < strlen(buffer); ++i ) fprintf ( log, "=" );
+ fprintf ( log, "\n%s", buffer );
+ fprintf ( log, "// Native %s endian\n", (kBigEndianHost ? "big" : "little") );
+
+ try {
+
+ DoTest ( log );
+
+ } catch ( ... ) {
+
+ fprintf ( log, "\n## Caught unexpected exception\n" );
+ return -1;
+
+ }
+
+ time ( &now );
+ sprintf ( buffer, "// Finished test for Unicode conversion correctness, %s", ctime ( &now ) );
+
+ fprintf ( log, "\n// " );
+ for ( size_t i = 4; i < strlen(buffer); ++i ) fprintf ( log, "=" );
+ fprintf ( log, "\n%s\n", buffer );
+
+ fclose ( log );
+ return 0;
+
+}
diff --git a/samples/source/UnicodeParseSerialize.cpp b/samples/source/UnicodeParseSerialize.cpp
new file mode 100644
index 0000000..5f0d463
--- /dev/null
+++ b/samples/source/UnicodeParseSerialize.cpp
@@ -0,0 +1,512 @@
+// =================================================================================================
+//
+// A thorough test for UTF-16 and UTF-32 serialization and parsing. It assumes the basic Unicode
+// conversion functions are working - they have their own exhaustive test.
+//
+// =================================================================================================
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+#include <ctime>
+
+#include <cstdlib>
+#include <cerrno>
+#include <stdexcept>
+#include <cassert>
+
+#define TXMP_STRING_TYPE std::string
+#include "XMP.hpp"
+#include "XMP.incl_cpp"
+
+#include "source/EndianUtils.hpp"
+#include "source/UnicodeConversions.hpp"
+#include "source/UnicodeConversions.cpp"
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+using namespace std;
+
+#if WIN_ENV
+ #pragma warning ( disable : 4701 ) // local variable may be used without having been initialized
+#endif
+
+// =================================================================================================
+
+#define IncludeUTF32 0 // *** UTF-32 parsing isn't working at the moment, Expat seems to not handle it.
+
+#define kCodePointCount 0x110000
+
+UTF8Unit sU8 [kCodePointCount*4 + 8];
+UTF16Unit sU16 [kCodePointCount*2 + 4];
+UTF32Unit sU32 [kCodePointCount + 2];
+
+static FILE * sLogFile;
+
+static const char * kNS1 = "ns:test1/";
+
+static const char * kSimpleRDF =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:kSimpleRDF/' xmlns:ns1='ns:test1/' xmlns:ns2='ns:test2/'>"
+ ""
+ " <ns1:SimpleProp>Simple value</ns1:SimpleProp>"
+ ""
+ " <ns1:ArrayProp>"
+ " <rdf:Bag>"
+ " <rdf:li>Item1 value</rdf:li>"
+ " <rdf:li>Item2 value</rdf:li>"
+ " </rdf:Bag>"
+ " </ns1:ArrayProp>"
+ ""
+ " <ns1:StructProp rdf:parseType='Resource'>"
+ " <ns2:Field1>Field1 value</ns2:Field1>"
+ " <ns2:Field2>Field2 value</ns2:Field2>"
+ " </ns1:StructProp>"
+ ""
+ " <ns1:QualProp rdf:parseType='Resource'>"
+ " <rdf:value>Prop value</rdf:value>"
+ " <ns2:Qual>Qual value</ns2:Qual>"
+ " </ns1:QualProp>"
+ ""
+ " <ns1:AltTextProp>"
+ " <rdf:Alt>"
+ " <rdf:li xml:lang='x-one'>x-one value</rdf:li>"
+ " <rdf:li xml:lang='x-two'>x-two value</rdf:li>"
+ " </rdf:Alt>"
+ " </ns1:AltTextProp>"
+ ""
+ " <ns1:ArrayOfStructProp>"
+ " <rdf:Bag>"
+ " <rdf:li rdf:parseType='Resource'>"
+ " <ns2:Field1>Item-1</ns2:Field1>"
+ " <ns2:Field2>Field 1.2 value</ns2:Field2>"
+ " </rdf:li>"
+ " <rdf:li rdf:parseType='Resource'>"
+ " <ns2:Field1>Item-2</ns2:Field1>"
+ " <ns2:Field2>Field 2.2 value</ns2:Field2>"
+ " </rdf:li>"
+ " </rdf:Bag>"
+ " </ns1:ArrayOfStructProp>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+// =================================================================================================
+
+static XMP_Status DumpToString ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ std::string * dumpString = static_cast < std::string * > ( refCon );
+ dumpString->append ( outStr, outLen );
+ return 0;
+}
+
+// =================================================================================================
+
+static XMP_Status DumpToFile ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ FILE * outFile = static_cast < FILE * > ( refCon );
+ fwrite ( outStr, 1, outLen, outFile );
+ return 0;
+}
+
+// =================================================================================================
+
+static void PrintXMPErrorInfo ( const XMP_Error & excep, const char * title )
+{
+ XMP_Int32 id = excep.GetID();
+ const char * message = excep.GetErrMsg();
+ fprintf ( sLogFile, "%s\n", title );
+ fprintf ( sLogFile, " #%d : %s\n", id, message );
+}
+
+// =================================================================================================
+
+static void FullUnicodeParse ( FILE * log, const char * encoding, size_t bufferSize,
+ const std::string & packet, const std::string & fullUnicode )
+{
+ if ( bufferSize > sizeof(sU32) ) {
+ fprintf ( log, "#ERROR: FullUnicodeParse buffer overrun for %s, %d byte buffers\n", encoding, bufferSize );
+ return;
+ }
+
+ SXMPMeta meta;
+ try {
+ memset ( sU32, -1, sizeof(sU32) );
+ for ( size_t i = 0; i < packet.size(); i += bufferSize ) {
+ size_t count = bufferSize;
+ if ( count > (packet.size() - i) ) count = packet.size() - i;
+ memcpy ( sU32, &packet[i], count );
+ meta.ParseFromBuffer ( XMP_StringPtr(sU32), count, kXMP_ParseMoreBuffers );
+ }
+ meta.ParseFromBuffer ( XMP_StringPtr(sU32), 0 );
+ } catch ( XMP_Error& excep ) {
+ char message [200];
+ sprintf ( message, "#ERROR: Full Unicode parsing error for %s, %d byte buffers", encoding, bufferSize );
+ PrintXMPErrorInfo ( excep, message );
+ return;
+ }
+
+ std::string value;
+ bool found = meta.GetProperty ( kNS1, "FullUnicode", &value, 0 );
+ if ( (! found) || (value != fullUnicode) ) fprintf ( log, "#ERROR: Failed to get full Unicode value for %s, %d byte buffers\n", encoding, bufferSize );
+
+} // FullUnicodeParse
+
+// =================================================================================================
+
+static void DoTest ( FILE * log )
+{
+ SXMPMeta meta;
+ size_t u8Count, u32Count;
+ SXMPMeta meta8, meta16b, meta16l, meta32b, meta32l;
+ std::string u8Packet, u16bPacket, u16lPacket, u32bPacket, u32lPacket;
+
+ InitializeUnicodeConversions();
+
+ // ---------------------------------------------------------------------------------------------
+
+ fprintf ( log, "// ------------------------------------------------\n" );
+ fprintf ( log, "// Test basic serialization and parsing using ASCII\n\n" );
+
+ // ----------------------------------------------------
+ // Create basic ASCII packets in each of the encodings.
+
+ meta.ParseFromBuffer ( kSimpleRDF, kXMP_UseNullTermination );
+
+ meta.SerializeToBuffer ( &u8Packet, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF8) );
+ meta.SerializeToBuffer ( &u16bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Big) );
+ meta.SerializeToBuffer ( &u16lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Little) );
+ meta.SerializeToBuffer ( &u32bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Big) );
+ meta.SerializeToBuffer ( &u32lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Little) );
+
+ #if 0
+ FILE* dump;
+ dump = fopen ( "u8Packet.txt", "w" );
+ fwrite ( u8Packet.c_str(), 1, u8Packet.size(), dump );
+ fclose ( dump );
+ dump = fopen ( "u16bPacket.txt", "w" );
+ fwrite ( u16bPacket.c_str(), 1, u16bPacket.size(), dump );
+ fclose ( dump );
+ dump = fopen ( "u16lPacket.txt", "w" );
+ fwrite ( u16lPacket.c_str(), 1, u16lPacket.size(), dump );
+ fclose ( dump );
+ dump = fopen ( "u32bPacket.txt", "w" );
+ fwrite ( u32bPacket.c_str(), 1, u32bPacket.size(), dump );
+ fclose ( dump );
+ dump = fopen ( "u32lPacket.txt", "w" );
+ fwrite ( u32lPacket.c_str(), 1, u32lPacket.size(), dump );
+ fclose ( dump );
+ #endif
+
+ // Verify the character form. The conversion functions are tested separately.
+
+ const char * ptr;
+
+ ptr = u8Packet.c_str();
+ fprintf ( log, "UTF-8 : %d : %.2X %.2X \"%.10s...\"\n", u8Packet.size(), *ptr, *(ptr+1), ptr );
+
+ ptr = u16bPacket.c_str();
+ fprintf ( log, "UTF-16BE : %d : %.2X %.2X %.2X\n", u16bPacket.size(), *ptr, *(ptr+1), *(ptr+2) );
+ ptr = u16lPacket.c_str();
+ fprintf ( log, "UTF-16LE : %d : %.2X %.2X %.2X\n", u16lPacket.size(), *ptr, *(ptr+1), *(ptr+2) );
+
+ ptr = u32bPacket.c_str();
+ fprintf ( log, "UTF-32BE : %d : %.2X %.2X %.2X %.2X %.2X\n", u32bPacket.size(), *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4) );
+ ptr = u32lPacket.c_str();
+ fprintf ( log, "UTF-32LE : %d : %.2X %.2X %.2X %.2X %.2X\n", u32lPacket.size(), *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4) );
+
+ fprintf ( log, "\nBasic serialization tests done\n" );
+
+ // -------------------------------------------------
+ // Verify round trip reparsing of the basic packets.
+
+ std::string origDump, rtDump;
+
+ meta.DumpObject ( DumpToString, &origDump );
+ fprintf ( log, "Original dump\n%s\n", origDump.c_str() );
+
+ try {
+ meta8.ParseFromBuffer ( u8Packet.c_str(), u8Packet.size() );
+ meta16b.ParseFromBuffer ( u16bPacket.c_str(), u16bPacket.size() );
+ meta16l.ParseFromBuffer ( u16lPacket.c_str(), u16lPacket.size() );
+ meta32b.ParseFromBuffer ( u32bPacket.c_str(), u32bPacket.size() );
+ meta32l.ParseFromBuffer ( u32lPacket.c_str(), u32lPacket.size() );
+ } catch ( XMP_Error& excep ) {
+ PrintXMPErrorInfo ( excep, "## Caught reparsing exception" );
+ fprintf ( log, "\n" );
+ }
+
+ #if 0
+ fprintf ( log, "After UTF-8 roundtrip\n" );
+ meta8.DumpObject ( DumpToFile, log );
+ fprintf ( log, "\nAfter UTF-16 BE roundtrip\n" );
+ meta16b.DumpObject ( DumpToFile, log );
+ fprintf ( log, "\nAfter UTF-16 LE roundtrip\n" );
+ meta16l.DumpObject ( DumpToFile, log );
+ fprintf ( log, "\nAfter UTF-32 BE roundtrip\n" );
+ meta32b.DumpObject ( DumpToFile, log );
+ fprintf ( log, "\nAfter UTF-32 LE roundtrip\n" );
+ meta32l.DumpObject ( DumpToFile, log );
+ #endif
+
+ rtDump.clear();
+ meta8.DumpObject ( DumpToString, &rtDump );
+ if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-8\n%s\n", rtDump.c_str() );
+
+ rtDump.clear();
+ meta16b.DumpObject ( DumpToString, &rtDump );
+ if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-16BE\n%s\n", rtDump.c_str() );
+
+ rtDump.clear();
+ meta16l.DumpObject ( DumpToString, &rtDump );
+ if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-16LE\n%s\n", rtDump.c_str() );
+
+ #if IncludeUTF32
+
+ rtDump.clear();
+ meta32b.DumpObject ( DumpToString, &rtDump );
+ if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-32BE\n%s\n", rtDump.c_str() );
+
+ rtDump.clear();
+ meta32l.DumpObject ( DumpToString, &rtDump );
+ if ( rtDump != origDump ) fprintf ( log, "#ERROR: Roundtrip failure for UTF-32LE\n%s\n", rtDump.c_str() );
+
+ #endif
+
+ fprintf ( log, "Basic round-trip parsing tests done\n\n" );
+
+ // ---------------------------------------------------------------------------------------------
+
+ fprintf ( log, "// --------------------------------------------------\n" );
+ fprintf ( log, "// Test parse buffering logic using full Unicode data\n\n" );
+
+ // --------------------------------------------------------------------------------------------
+ // Construct the packets to parse in all encodings. There is just one property with a value
+ // containing all of the Unicode representations. This isn't all of the Unicode characters, but
+ // is more than enough to establish correctness of the buffering logic. It is almost everything
+ // in the BMP, plus the range U+100000..U+10FFFF beyond the BMP. Doing all Unicode characters
+ // takes far to long to execute and does not provide additional confidence. Skip ASCII controls,
+ // they are not allowed in XML and get changed to spaces by SetProperty. Skip U+FFFE and U+FFFF,
+ // the expat parser rejects them.
+
+ #define kTab 0x09
+ #define kLF 0x0A
+ #define kCR 0x0D
+
+ size_t i;
+ UTF32Unit cp;
+ sU32[0] = kTab; sU32[1] = kLF; sU32[2] = kCR;
+ for ( i = 3, cp = 0x20; cp < 0x7F; ++i, ++cp ) sU32[i] = cp;
+ for ( cp = 0x80; cp < 0xD800; ++i, ++cp ) sU32[i] = cp;
+ for ( cp = 0xE000; cp < 0xFFFE; ++i, ++cp ) sU32[i] = cp;
+ for ( cp = 0x100000; cp < 0x110000; ++i, ++cp ) sU32[i] = cp;
+ u32Count = i;
+ assert ( u32Count == (3 + (0x7F-0x20) + (0xD800-0x80) + (0xFFFE - 0xE000) + (0x110000-0x100000)) );
+
+ if ( kBigEndianHost ) {
+ UTF32BE_to_UTF8 ( sU32, u32Count, sU8, sizeof(sU8), &i, &u8Count );
+ } else {
+ UTF32LE_to_UTF8 ( sU32, u32Count, sU8, sizeof(sU8), &i, &u8Count );
+ }
+ if ( i != u32Count ) fprintf ( log, "#ERROR: Failed to convert full UTF-32 buffer\n" );
+ assert ( u8Count == (3 + (0x7F-0x20) + 2*(0x800-0x80) + 3*(0xD800-0x800) + 3*(0xFFFE - 0xE000) + 4*(0x110000-0x100000)) );
+ sU8[u8Count] = 0;
+
+ std::string fullUnicode;
+ SXMPUtils::RemoveProperties ( &meta, "", "", kXMPUI_DoAllProperties );
+ meta.SetProperty ( kNS1, "FullUnicode", XMP_StringPtr(sU8) );
+ meta.GetProperty ( kNS1, "FullUnicode", &fullUnicode, 0 );
+ if ( (fullUnicode.size() != u8Count) || (fullUnicode != XMP_StringPtr(sU8)) ) {
+ fprintf ( log, "#ERROR: Failed to set full UTF-8 value\n" );
+ if ( (fullUnicode.size() != u8Count) ) {
+ fprintf ( log, " Size mismatch, want %d, got %d\n", u8Count, fullUnicode.size() );
+ } else {
+ for ( size_t b = 0; b < u8Count; ++b ) {
+ if ( fullUnicode[b] != sU8[b] ) fprintf ( log, " Byte mismatch at %d\n", b );
+ }
+ }
+ }
+
+ u8Packet.clear();
+ u16bPacket.clear();
+ u16lPacket.clear();
+ u32bPacket.clear();
+ u32lPacket.clear();
+
+ meta.SerializeToBuffer ( &u8Packet, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF8) );
+ meta.SerializeToBuffer ( &u16bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Big) );
+ meta.SerializeToBuffer ( &u16lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF16Little) );
+ #if IncludeUTF32
+ meta.SerializeToBuffer ( &u32bPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Big) );
+ meta.SerializeToBuffer ( &u32lPacket, (kXMP_OmitPacketWrapper | kXMP_EncodeUTF32Little) );
+ #endif
+
+ // ---------------------------------------------------------------------
+ // Parse the whole packet as a sanity check, then at a variety of sizes.
+
+ FullUnicodeParse ( log, "UTF-8", u8Packet.size(), u8Packet, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16BE", u16bPacket.size(), u16bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16LE", u16lPacket.size(), u16lPacket, fullUnicode );
+ #if IncludeUTF32
+ FullUnicodeParse ( log, "UTF-32BE", u32bPacket.size(), u32bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-32LE", u32lPacket.size(), u32lPacket, fullUnicode );
+ #endif
+ fprintf ( log, "Full packet, no BOM, buffered parsing tests done\n" );
+
+#if 0 // Skip the partial buffer tests, there seem to be problems, but no client uses partial buffers.
+
+ for ( i = 1; i <= 3; ++i ) {
+ FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode );
+ #if IncludeUTF32
+ FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode );
+ #endif
+ fprintf ( log, "%d byte buffers, no BOM, buffered parsing tests done\n", i );
+ }
+
+ for ( i = 4; i <= 16; i *= 2 ) {
+ FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode );
+ #if IncludeUTF32
+ FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode );
+ #endif
+ fprintf ( log, "%d byte buffers, no BOM, buffered parsing tests done\n", i );
+ }
+
+#endif
+
+ fprintf ( log, "\n" );
+
+ // -----------------------------------------------------------------------
+ // Redo the buffered parsing tests, now with a leading BOM in the packets.
+
+ u8Packet.insert ( 0, "\xEF\xBB\xBF", 3 );
+
+ UTF32Unit NatBOM = 0x0000FEFF;
+ UTF32Unit SwapBOM = 0xFFFE0000;
+
+ if ( kBigEndianHost ) {
+ u16bPacket.insert ( 0, XMP_StringPtr(&NatBOM)+2, 2 );
+ u16lPacket.insert ( 0, XMP_StringPtr(&SwapBOM), 2 );
+ u32bPacket.insert ( 0, XMP_StringPtr(&NatBOM), 4 );
+ u32lPacket.insert ( 0, XMP_StringPtr(&SwapBOM), 4 );
+ } else {
+ u16lPacket.insert ( 0, XMP_StringPtr(&NatBOM), 2 );
+ u16bPacket.insert ( 0, XMP_StringPtr(&SwapBOM)+2, 2 );
+ u32lPacket.insert ( 0, XMP_StringPtr(&NatBOM), 4 );
+ u32bPacket.insert ( 0, XMP_StringPtr(&SwapBOM), 4 );
+ }
+
+ FullUnicodeParse ( log, "UTF-8", u8Packet.size(), u8Packet, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16BE", u16bPacket.size(), u16bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16LE", u16lPacket.size(), u16lPacket, fullUnicode );
+ #if IncludeUTF32
+ FullUnicodeParse ( log, "UTF-32BE", u32bPacket.size(), u32bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-32LE", u32lPacket.size(), u32lPacket, fullUnicode );
+ #endif
+ fprintf ( log, "Full packet, leading BOM, buffered parsing tests done\n" );
+
+#if 0 // Skip the partial buffer tests, there seem to be problems, but no client uses partial buffers.
+
+ for ( i = 1; i <= 3; ++i ) {
+ FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode );
+ #if IncludeUTF32
+ FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode );
+ #endif
+ fprintf ( log, "%d byte buffers, leading BOM, buffered parsing tests done\n", i );
+ }
+
+ for ( i = 4; i <= 16; i *= 2 ) {
+ FullUnicodeParse ( log, "UTF-8", i, u8Packet, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16BE", i, u16bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-16LE", i, u16lPacket, fullUnicode );
+ #if IncludeUTF32
+ FullUnicodeParse ( log, "UTF-32BE", i, u32bPacket, fullUnicode );
+ FullUnicodeParse ( log, "UTF-32LE", i, u32lPacket, fullUnicode );
+ #endif
+ fprintf ( log, "%d byte buffers, leading BOM, buffered parsing tests done\n", i );
+ }
+
+#endif
+
+ fprintf ( log, "\n" );
+
+} // DoTest
+
+// =================================================================================================
+
+extern "C" int main ( void )
+{
+ int result = 0;
+ char buffer [1000];
+
+ sLogFile = stdout;
+
+ time_t now;
+ time ( &now );
+ sprintf ( buffer, "// Starting test for UTF-16 and UTF-32 serialization and parsing, %s", ctime ( &now ) );
+
+ fprintf ( sLogFile, "// " );
+ for ( int i = 4; i < strlen(buffer); ++i ) fprintf ( sLogFile, "=" );
+ fprintf ( sLogFile, "\n%s", buffer );
+
+ fprintf ( sLogFile, "// =====================================================================================\n" );
+ fprintf ( sLogFile, "// A thorough test for UTF-16 and UTF-32 serialization and parsing. It assumes the basic\n" );
+ fprintf ( sLogFile, "// Unicode conversion functions are working - they have their own exhaustive test.\n\n" );
+
+ #if ! IncludeUTF32
+ fprintf ( sLogFile, "// ** Skipping UTF-32 tests, Expat seems to not handle it.\n\n" );
+ #endif
+
+ #if 0
+ if ( sLogFile == stdout ) {
+ // Use this to be able to move the app window away from debugger windows.
+ fprintf ( sLogFile, "Move window, type return to continue" );
+ fread ( buffer, 1, 1, stdin );
+ }
+ #endif
+
+ try {
+
+ if ( ! SXMPMeta::Initialize() ) {
+ fprintf ( sLogFile, "\n## SXMPMeta::Initialize failed!\n" );
+ return -1;
+ }
+
+ DoTest ( sLogFile );
+
+ SXMPMeta::Terminate();
+
+ } catch ( XMP_Error& excep ) {
+
+ PrintXMPErrorInfo ( excep, "\n## Unhandled XMP_Error exception" );
+
+ } catch ( ... ) {
+
+ fprintf ( sLogFile, "\n## Unexpected exception\n" );
+ return -1;
+
+ }
+
+ time ( &now );
+ sprintf ( buffer, "// Finished test for UTF-16 and UTF-32 serialization and parsing, %s", ctime ( &now ) );
+
+ fprintf ( sLogFile, "// " );
+ for ( int i = 4; i < strlen(buffer); ++i ) fprintf ( sLogFile, "=" );
+ fprintf ( sLogFile, "\n%s\n", buffer );
+
+ fclose ( sLogFile );
+ return 0;
+
+}
diff --git a/samples/source/UnicodePerformance.cpp b/samples/source/UnicodePerformance.cpp
new file mode 100644
index 0000000..dac4ecd
--- /dev/null
+++ b/samples/source/UnicodePerformance.cpp
@@ -0,0 +1,310 @@
+// =================================================================================================
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <ctime>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cerrno>
+#include <stdexcept>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+using namespace std;
+
+#if WIN_ENV
+ #pragma warning ( disable : 4701 ) // local variable may be used without having been initialized
+#endif
+
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h"
+#include "public/include/XMP_Const.h"
+
+#include "source/EndianUtils.hpp"
+#include "source/UnicodeConversions.hpp"
+#include "source/UnicodeConversions.cpp"
+
+#define TestUnicodeConsortium 0
+
+#if TestUnicodeConsortium
+ #include "ConvertUTF.c" // The Unicode Consortium implementations.
+#endif
+
+// =================================================================================================
+
+#define kCodePointCount 0x110000
+
+UTF8Unit sU8 [kCodePointCount*4 + 8];
+UTF16Unit sU16 [kCodePointCount*2 + 4];
+UTF32Unit sU32 [kCodePointCount + 2];
+
+// =================================================================================================
+
+static UTF8_to_UTF16_Proc OurUTF8_to_UTF16; // ! Don't use static initialization, VS.Net strips it!
+static UTF8_to_UTF32_Proc OurUTF8_to_UTF32;
+static UTF16_to_UTF8_Proc OurUTF16_to_UTF8;
+static UTF16_to_UTF32_Proc OurUTF16_to_UTF32;
+static UTF32_to_UTF8_Proc OurUTF32_to_UTF8;
+static UTF32_to_UTF16_Proc OurUTF32_to_UTF16;
+
+// =================================================================================================
+
+static void ReportPerformance ( FILE * log, const char * content, const size_t u32Count, const size_t u16Count, const size_t u8Count )
+{
+ size_t inCount, outCount;
+ UTF32Unit * u32Ptr;
+ UTF16Unit * u16Ptr;
+ UTF8Unit * u8Ptr;
+
+ size_t i;
+ const size_t cycles = 100;
+ clock_t start, end;
+ double elapsed;
+
+ // --------------------------------------------------
+ fprintf ( log, "\n Adobe code over %s\n", content );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) OurUTF32_to_UTF8 ( sU32, u32Count, sU8, sizeof(sU8), &inCount, &outCount );
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF32_to_UTF8 : %.3f seconds\n", elapsed );
+ if ( (inCount != u32Count) || (outCount != u8Count) ) fprintf ( log, " *** Our UTF32_to_UTF8 count error, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) OurUTF32_to_UTF16 ( sU32, u32Count, sU16, sizeof(sU16), &inCount, &outCount );
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF32_to_UTF16 : %.3f seconds\n", elapsed );
+ if ( (inCount != u32Count) || (outCount != u16Count) ) fprintf ( log, " *** Our UTF32_to_UTF16 count error, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) OurUTF16_to_UTF8 ( sU16, u16Count, sU8, sizeof(sU8), &inCount, &outCount );
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF16_to_UTF8 : %.3f seconds\n", elapsed );
+ if ( (inCount != u16Count) || (outCount != u8Count) ) fprintf ( log, " *** Our UTF16_to_UTF8 count error, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) OurUTF16_to_UTF32 ( sU16, u16Count, sU32, sizeof(sU32), &inCount, &outCount );
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF16_to_UTF32 : %.3f seconds\n", elapsed );
+ if ( (inCount != u16Count) || (outCount != u32Count) ) fprintf ( log, " *** Our UTF16_to_UTF32 count error, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) OurUTF8_to_UTF16 ( sU8, u8Count, sU16, sizeof(sU16), &inCount, &outCount );
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF8_to_UTF16 : %.3f seconds\n", elapsed );
+ if ( (inCount != u8Count) || (outCount != u16Count) ) fprintf ( log, " *** Our UTF8_to_UTF16 count error, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) OurUTF8_to_UTF32 ( sU8, u8Count, sU32, sizeof(sU32), &inCount, &outCount );
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF8_to_UTF32 : %.3f seconds\n", elapsed );
+ if ( (inCount != u8Count) || (outCount != u32Count) ) fprintf ( log, " *** Our UTF8_to_UTF32 count error, %d -> %d\n", inCount, outCount );
+
+ #if TestUnicodeConsortium
+
+ // ---------------------------------------------------------------
+ fprintf ( log, "\n Unicode Consortium code over %s\n", content );
+
+ ConversionResult ucStatus;
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) {
+ u32Ptr = sU32; u8Ptr = sU8;
+ ucStatus = ConvertUTF32toUTF8 ( (const UTF32**)(&u32Ptr), (const UTF32*)(sU32+u32Count), &u8Ptr, sU8+sizeof(sU8), strictConversion );
+ }
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF32_to_UTF8 : %.3f seconds\n", elapsed );
+ inCount = u32Ptr - sU32; outCount = u8Ptr - sU8;
+ if ( ucStatus != conversionOK ) fprintf ( log, " *** UC ConvertUTF32toUTF8 status error, %d\n", ucStatus );
+ if ( (inCount != u32Count) || (outCount != u8Count) ) fprintf ( log, " *** UC ConvertUTF32toUTF8 count error, %d, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) {
+ u32Ptr = sU32; u16Ptr = sU16;
+ ucStatus = ConvertUTF32toUTF16 ( (const UTF32**)(&u32Ptr), (const UTF32*)(sU32+u32Count), &u16Ptr, sU16+sizeof(sU16), strictConversion );
+ }
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF32_to_UTF16 : %.3f seconds\n", elapsed );
+ inCount = u32Ptr - sU32; outCount = u16Ptr - sU16;
+ if ( ucStatus != conversionOK ) fprintf ( log, " *** UC ConvertUTF32toUTF16 status error, %d\n", ucStatus );
+ if ( (inCount != u32Count) || (outCount != u16Count) ) fprintf ( log, " *** UC ConvertUTF32toUTF16 count error, %d, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) {
+ u16Ptr = sU16; u8Ptr = sU8;
+ ucStatus = ConvertUTF16toUTF8 ( (const UTF16**)(&u16Ptr), (const UTF16*)(sU16+u16Count), &u8Ptr, sU8+sizeof(sU8), strictConversion );
+ }
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF16_to_UTF8 : %.3f seconds\n", elapsed );
+ inCount = u16Ptr - sU16; outCount = u8Ptr - sU8;
+ if ( ucStatus != conversionOK ) fprintf ( log, " *** UC ConvertUTF16toUTF8 status error, %d\n", ucStatus );
+ if ( (inCount != u16Count) || (outCount != u8Count) ) fprintf ( log, " *** UC ConvertUTF16toUTF8 count error, %d, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) {
+ u16Ptr = sU16; u32Ptr = sU32;
+ ucStatus = ConvertUTF16toUTF32 ( (const UTF16**)(&u16Ptr), (const UTF16*)(sU16+u16Count), &u32Ptr, sU32+sizeof(sU32), strictConversion );
+ }
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF16_to_UTF32 : %.3f seconds\n", elapsed );
+ inCount = u16Ptr - sU16; outCount = u32Ptr - sU32;
+ if ( ucStatus != conversionOK ) fprintf ( log, " *** UC ConvertUTF16toUTF32 status error, %d\n", ucStatus );
+ if ( (inCount != u16Count) || (outCount != u32Count) ) fprintf ( log, " *** UC ConvertUTF16toUTF32 count error, %d, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) {
+ u8Ptr = sU8; u16Ptr = sU16;
+ ucStatus = ConvertUTF8toUTF16 ( (const UTF8**)(&u8Ptr), (const UTF8*)(sU8+u8Count), &u16Ptr, sU16+sizeof(sU16), strictConversion );
+ }
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF8_to_UTF16 : %.3f seconds\n", elapsed );
+ inCount = u8Ptr - sU8; outCount = u16Ptr - sU16;
+ if ( ucStatus != conversionOK ) fprintf ( log, " *** UC ConvertUTF8toUTF16 status error, %d\n", ucStatus );
+ if ( (inCount != u8Count) || (outCount != u16Count) ) fprintf ( log, " *** UC ConvertUTF8toUTF16 count error, %d, %d -> %d\n", inCount, outCount );
+
+ start = clock();
+ for ( i = 0; i < cycles; ++i ) {
+ u8Ptr = sU8; u32Ptr = sU32;
+ ucStatus = ConvertUTF8toUTF32 ( (const UTF8**)(&u8Ptr), (const UTF8*)(sU8+u8Count), &u32Ptr, sU32+sizeof(sU32), strictConversion );
+ }
+ end = clock();
+ elapsed = double(end-start) / CLOCKS_PER_SEC;
+
+ fprintf ( log, " UTF8_to_UTF32 : %.3f seconds\n", elapsed );
+ inCount = u8Ptr - sU8; outCount = u32Ptr - sU32;
+ if ( ucStatus != conversionOK ) fprintf ( log, " *** UC ConvertUTF8toUTF32 status error, %d\n", ucStatus );
+ if ( (inCount != u8Count) || (outCount != u32Count) ) fprintf ( log, " *** UC ConvertUTF8toUTF32 count error, %d, %d -> %d\n", inCount, outCount );
+
+ #endif
+
+} // ReportPerformance
+
+// =================================================================================================
+
+static void ComparePerformance ( FILE * log )
+{
+ size_t i, u32Count, u16Count, u8Count;
+ UTF32Unit cp;
+
+ if ( kBigEndianHost ) {
+ OurUTF8_to_UTF16 = UTF8_to_UTF16BE;
+ OurUTF8_to_UTF32 = UTF8_to_UTF32BE;
+ OurUTF16_to_UTF8 = UTF16BE_to_UTF8;
+ OurUTF16_to_UTF32 = UTF16BE_to_UTF32BE;
+ OurUTF32_to_UTF8 = UTF32BE_to_UTF8;
+ OurUTF32_to_UTF16 = UTF32BE_to_UTF16BE;
+ } else {
+ OurUTF8_to_UTF16 = UTF8_to_UTF16LE;
+ OurUTF8_to_UTF32 = UTF8_to_UTF32LE;
+ OurUTF16_to_UTF8 = UTF16LE_to_UTF8;
+ OurUTF16_to_UTF32 = UTF16LE_to_UTF32LE;
+ OurUTF32_to_UTF8 = UTF32LE_to_UTF8;
+ OurUTF32_to_UTF16 = UTF32LE_to_UTF16LE;
+ }
+
+ for ( i = 0, cp = 0; cp < 0xD800; ++i, ++cp ) sU32[i] = cp; // Measure using the full Unicode set.
+ for ( cp = 0xE000; cp < 0x110000; ++i, ++cp ) sU32[i] = cp;
+ u32Count = 0xD800 + (0x110000 - 0xE000);
+ u16Count = 0xD800 + (0x10000 - 0xE000) + (0x110000 - 0x10000)*2;
+ u8Count = 0x80 + (0x800 - 0x80)*2 + (0xD800 - 0x800)*3 + (0x10000 - 0xE000)*3 + (0x110000 - 0x10000)*4;
+ ReportPerformance ( log, "full Unicode set", u32Count, u16Count, u8Count );
+
+ for ( i = 0; i < 0x110000; ++i ) sU32[i] = i & 0x7F; // Measure using just ASCII.
+ u32Count = 0x110000;
+ u16Count = 0x110000;
+ u8Count = 0x110000;
+ ReportPerformance ( log, "just ASCII", u32Count, u16Count, u8Count );
+
+ for ( i = 0; i < 0x110000; ++i ) sU32[i] = 0x4000 + (i & 0x7FFF); // Measure using just non-ASCII inside the BMP.
+ u32Count = 0x110000;
+ u16Count = 0x110000;
+ u8Count = 0x110000*3;
+ ReportPerformance ( log, "just non-ASCII inside the BMP", u32Count, u16Count, u8Count );
+
+ for ( i = 0; i < 0x110000; ++i ) sU32[i] = 0x40000 + (i & 0xFFFF); // Measure using just outside the BMP.
+ u32Count = 0x110000;
+ u16Count = 0x110000*2;
+ u8Count = 0x110000*4;
+ ReportPerformance ( log, "just outside the BMP", u32Count, u16Count, u8Count );
+
+} // ComparePerformance
+
+// =================================================================================================
+
+static void DoTest ( FILE * log )
+{
+
+ InitializeUnicodeConversions();
+ ComparePerformance ( log );
+
+} // DoTest
+
+// =================================================================================================
+
+extern "C" int main ( void )
+{
+ char buffer [1000];
+
+ #if !XMP_AutomatedTestBuild
+ FILE * log = stdout;
+ #else
+ FILE * log = fopen ( "TestUnicode.out", "wb" );
+ #endif
+
+ time_t now;
+ time ( &now );
+ sprintf ( buffer, "// Starting test for Unicode conversion performance, %s", ctime ( &now ) );
+
+ fprintf ( log, "// " );
+ for ( size_t i = 4; i < strlen(buffer); ++i ) fprintf ( log, "=" );
+ fprintf ( log, "\n%s", buffer );
+ fprintf ( log, "// Native %s endian\n", (kBigEndianHost ? "big" : "little") );
+
+ try {
+
+ DoTest ( log );
+
+ } catch ( ... ) {
+
+ fprintf ( log, "\n## Caught unexpected exception\n" );
+ return -1;
+
+ }
+
+ time ( &now );
+ sprintf ( buffer, "// Finished test for Unicode conversion performance, %s", ctime ( &now ) );
+
+ fprintf ( log, "\n// " );
+ for ( size_t i = 4; i < strlen(buffer); ++i ) fprintf ( log, "=" );
+ fprintf ( log, "\n%s\n", buffer );
+
+ fclose ( log );
+ return 0;
+
+}
diff --git a/samples/source/XMPCoreCoverage.cpp b/samples/source/XMPCoreCoverage.cpp
new file mode 100644
index 0000000..291bd27
--- /dev/null
+++ b/samples/source/XMPCoreCoverage.cpp
@@ -0,0 +1,1876 @@
+// =================================================================================================
+// Copyright 2002 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Demonstrates syntax and usage by exercising most of the API functions of XMPCore Toolkit SDK component,
+* using a sample XMP Packet that contains all of the different property and attribute types.
+*/
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+
+#include <cstdlib>
+#include <cerrno>
+#include <ctime>
+
+#define TXMP_STRING_TYPE std::string
+
+#include "public/include/XMP.hpp"
+#include "public/include/XMP.incl_cpp"
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+using namespace std;
+
+#if WIN_ENV
+ #pragma warning ( disable : 4100 ) // ignore unused variable
+ #pragma warning ( disable : 4127 ) // conditional expression is constant
+ #pragma warning ( disable : 4267 ) // possible loss of data (temporary for 64-bit builds)
+ #pragma warning ( disable : 4505 ) // unreferenced local function has been removed
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+// =================================================================================================
+
+static const char * kNS1 = "ns:test1/";
+static const char * kNS2 = "ns:test2/";
+
+static const char * kRDFCoverage =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kRDFCoverage' xmlns:ns1='ns:test1/' xmlns:ns2='ns:test2/'>"
+ ""
+ " <ns1:SimpleProp1>Simple1 value</ns1:SimpleProp1>"
+ " <ns1:SimpleProp2 xml:lang='x-default'>Simple2 value</ns1:SimpleProp2>"
+ ""
+ " <ns1:ArrayProp1>"
+ " <rdf:Bag>"
+ " <rdf:li>Item1.1 value</rdf:li>"
+ " <rdf:li>Item1.2 value</rdf:li>"
+ " </rdf:Bag>"
+ " </ns1:ArrayProp1>"
+ ""
+ " <ns1:ArrayProp2>"
+ " <rdf:Alt>"
+ " <rdf:li xml:lang='x-one'>Item2.1 value</rdf:li>"
+ " <rdf:li xml:lang='x-two'>Item2.2 value</rdf:li>"
+ " </rdf:Alt>"
+ " </ns1:ArrayProp2>"
+ ""
+ " <ns1:ArrayProp3>"
+ " <rdf:Alt>"
+ " <rdf:li xml:lang='x-one'>Item3.1 value</rdf:li>"
+ " <rdf:li>Item3.2 value</rdf:li>"
+ " </rdf:Alt>"
+ " </ns1:ArrayProp3>"
+ ""
+ " <ns1:ArrayProp4>"
+ " <rdf:Alt>"
+ " <rdf:li>Item4.1 value</rdf:li>"
+ " <rdf:li xml:lang='x-two'>Item4.2 value</rdf:li>"
+ " </rdf:Alt>"
+ " </ns1:ArrayProp4>"
+ ""
+ " <ns1:ArrayProp5>"
+ " <rdf:Alt>"
+ " <rdf:li xml:lang='x-xxx'>Item5.1 value</rdf:li>"
+ " <rdf:li xml:lang='x-xxx'>Item5.2 value</rdf:li>"
+ " </rdf:Alt>"
+ " </ns1:ArrayProp5>"
+ ""
+ " <ns1:StructProp rdf:parseType='Resource'>"
+ " <ns2:Field1>Field1 value</ns2:Field1>"
+ " <ns2:Field2>Field2 value</ns2:Field2>"
+ " </ns1:StructProp>"
+ ""
+ " <ns1:QualProp1 rdf:parseType='Resource'>"
+ " <rdf:value>Prop value</rdf:value>"
+ " <ns2:Qual>Qual value</ns2:Qual>"
+ " </ns1:QualProp1>"
+ ""
+ " <ns1:QualProp2 rdf:parseType='Resource'>"
+ " <rdf:value xml:lang='x-default'>Prop value</rdf:value>"
+ " <ns2:Qual>Qual value</ns2:Qual>"
+ " </ns1:QualProp2>"
+ ""
+ " <!-- NOTE: QualProp3 is not quite kosher. Normally a qualifier on a struct is attached to the -->"
+ " <!-- struct node in the XMP tree, and the same for an array. See QualProp4 and QualProp5. But -->"
+ " <!-- for the pseudo-struct of a qualified simple property there is no final struct node that -->"
+ " <!-- can own the qualifier. Instead the qualifier is attached to the value. The alternative -->"
+ " <!-- of attaching the qualifier to the value and all other qualifiers is not compelling. This -->"
+ " <!-- issue only arises for xml:lang, it is the only qualifier that RDF has as an attribute. -->"
+ ""
+ " <ns1:QualProp3 xml:lang='x-default' rdf:parseType='Resource'>"
+ " <rdf:value>Prop value</rdf:value>"
+ " <ns2:Qual>Qual value</ns2:Qual>"
+ " </ns1:QualProp3>"
+ ""
+ " <ns1:QualProp4 xml:lang='x-default' rdf:parseType='Resource'>"
+ " <ns2:Field1>Field1 value</ns2:Field1>"
+ " <ns2:Field2>Field2 value</ns2:Field2>"
+ " </ns1:QualProp4>"
+ ""
+ " <ns1:QualProp5 xml:lang='x-default'>"
+ " <rdf:Bag>"
+ " <rdf:li>Item1.1 value</rdf:li>"
+ " <rdf:li>Item1.2 value</rdf:li>"
+ " </rdf:Bag>"
+ " </ns1:QualProp5>"
+ ""
+ " <ns2:NestedStructProp rdf:parseType='Resource'>"
+ " <ns1:Outer rdf:parseType='Resource'>"
+ " <ns1:Middle rdf:parseType='Resource'>"
+ " <ns1:Inner rdf:parseType='Resource'>"
+ " <ns1:Field1>Field1 value</ns1:Field1>"
+ " <ns2:Field2>Field2 value</ns2:Field2>"
+ " </ns1:Inner>"
+ " </ns1:Middle>"
+ " </ns1:Outer>"
+ " </ns2:NestedStructProp>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+static const char * kSimpleRDF =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kSimpleRDF' xmlns:ns1='ns:test1/' xmlns:ns2='ns:test2/'>"
+ ""
+ " <ns1:SimpleProp>Simple value</ns1:SimpleProp>"
+ ""
+ " <ns1:ArrayProp>"
+ " <rdf:Bag>"
+ " <rdf:li>Item1 value</rdf:li>"
+ " <rdf:li>Item2 value</rdf:li>"
+ " </rdf:Bag>"
+ " </ns1:ArrayProp>"
+ ""
+ " <ns1:StructProp rdf:parseType='Resource'>"
+ " <ns2:Field1>Field1 value</ns2:Field1>"
+ " <ns2:Field2>Field2 value</ns2:Field2>"
+ " </ns1:StructProp>"
+ ""
+ " <ns1:QualProp rdf:parseType='Resource'>"
+ " <rdf:value>Prop value</rdf:value>"
+ " <ns2:Qual>Qual value</ns2:Qual>"
+ " </ns1:QualProp>"
+ ""
+ " <ns1:AltTextProp>"
+ " <rdf:Alt>"
+ " <rdf:li xml:lang='x-one'>x-one value</rdf:li>"
+ " <rdf:li xml:lang='x-two'>x-two value</rdf:li>"
+ " </rdf:Alt>"
+ " </ns1:AltTextProp>"
+ ""
+ " <ns1:ArrayOfStructProp>"
+ " <rdf:Bag>"
+ " <rdf:li rdf:parseType='Resource'>"
+ " <ns2:Field1>Item-1</ns2:Field1>"
+ " <ns2:Field2>Field 1.2 value</ns2:Field2>"
+ " </rdf:li>"
+ " <rdf:li rdf:parseType='Resource'>"
+ " <ns2:Field1>Item-2</ns2:Field1>"
+ " <ns2:Field2>Field 2.2 value</ns2:Field2>"
+ " </rdf:li>"
+ " </rdf:Bag>"
+ " </ns1:ArrayOfStructProp>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+static const char * kNamespaceRDF =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kNamespaceRDF' xmlns:ns1='ns:test1/'>"
+ ""
+ " <ns1:NestedStructProp rdf:parseType='Resource'>"
+ " <ns2:Outer rdf:parseType='Resource' xmlns:ns2='ns:test2/' xmlns:ns3='ns:test3/'>"
+ " <ns3:Middle rdf:parseType='Resource' xmlns:ns4='ns:test4/'>"
+ " <ns4:Inner rdf:parseType='Resource' xmlns:ns5='ns:test5/' xmlns:ns6='ns:test6/'>"
+ " <ns5:Field1>Field1 value</ns5:Field1>"
+ " <ns6:Field2>Field2 value</ns6:Field2>"
+ " </ns4:Inner>"
+ " </ns3:Middle>"
+ " </ns2:Outer>"
+ " </ns1:NestedStructProp>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+static const char * kXMPMetaRDF =
+ "<x:Outermost xmlns:x='adobe:ns:meta/'>"
+ ""
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kBogusLeadingRDF' xmlns:ns1='ns:test1/'>"
+ " <ns1:BogusLeadingProp>bogus packet</ns1:BogusLeadingProp>"
+ " </rdf:Description>"
+ "</rdf:RDF>"
+ ""
+ "<x:xmpmeta>"
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kXMPMetaRDF' xmlns:ns1='ns:test1/'>"
+ " <ns1:XMPMetaProp>xmpmeta packet</ns1:XMPMetaProp>"
+ " </rdf:Description>"
+ "</rdf:RDF>"
+ "</x:xmpmeta>"
+ ""
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kBogusTrailingRDF' xmlns:ns1='ns:test1/'>"
+ " <ns1:BogusTrailingProp>bogus packet</ns1:BogusTrailingProp>"
+ " </rdf:Description>"
+ "</rdf:RDF>"
+ ""
+ "</x:Outermost>";
+
+static const char * kNewlineRDF =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kNewlineRDF' xmlns:ns1='ns:test1/'>"
+ ""
+ " <ns1:HasCR>ASCII &#xD; CR</ns1:HasCR>"
+ " <ns1:HasLF>ASCII &#xA; LF</ns1:HasLF>"
+ " <ns1:HasCRLF>ASCII &#xD;&#xA; CRLF</ns1:HasCRLF>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+static const char * kInconsistentRDF =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kInconsistentRDF'"
+ " xmlns:pdf='http://ns.adobe.com/pdf/1.3/'"
+ " xmlns:xmp='http://ns.adobe.com/xap/1.0/'"
+ " xmlns:dc='http://purl.org/dc/elements/1.1/'>"
+ ""
+ " <pdf:Author>PDF Author</pdf:Author>"
+ " <xmp:Author>XMP Author</xmp:Author>"
+ ""
+ " <xmp:Authors>"
+ " <rdf:Seq>"
+ " <rdf:li>XMP Authors [1]</rdf:li>"
+ " </rdf:Seq>"
+ " </xmp:Authors>"
+ ""
+ " <dc:creator>"
+ " <rdf:Seq>"
+ " <rdf:li>DC Creator [1]</rdf:li>"
+ " </rdf:Seq>"
+ " </dc:creator>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+static const char * kDateTimeRDF =
+ "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+ " <rdf:Description rdf:about='Test:XMPCoreCoverage/kDateTimeRDF' xmlns:ns1='ns:test1/'>"
+ ""
+ " <ns1:Date1>2003</ns1:Date1>"
+ " <ns1:Date2>2003-12</ns1:Date2>"
+ " <ns1:Date3>2003-12-31</ns1:Date3>"
+ ""
+ " <ns1:Date4>2003-12-31T12:34Z</ns1:Date4>"
+ " <ns1:Date5>2003-12-31T12:34:56Z</ns1:Date5>"
+ ""
+ " <ns1:Date6>2003-12-31T12:34:56.001Z</ns1:Date6>"
+ " <ns1:Date7>2003-12-31T12:34:56.000000001Z</ns1:Date7>"
+ ""
+ " <ns1:Date8>2003-12-31T10:04:56-02:30</ns1:Date8>"
+ " <ns1:Date9>2003-12-31T15:49:56+03:15</ns1:Date9>"
+ ""
+ " </rdf:Description>"
+ "</rdf:RDF>";
+
+// =================================================================================================
+
+#define FoundOrNot(b) ((b) ? "found" : "not found")
+#define YesOrNo(b) ((b) ? "yes" : "no")
+
+// -------------------------------------------------------------------------------------------------
+
+static void FillDateTime ( XMP_DateTime * dateTime, XMP_Int32 year, XMP_Int32 month, XMP_Int32 day,
+ XMP_Int32 hour, XMP_Int32 minute, XMP_Int32 second,
+ XMP_Bool hasDate, XMP_Bool hasTime, XMP_Bool hasTimeZone,
+ XMP_Int8 tzSign, XMP_Int32 tzHour, XMP_Int32 tzMinute, XMP_Int32 nanoSecond )
+{
+
+ dateTime->year = year;
+ dateTime->month = month;
+ dateTime->day = day;
+ dateTime->hour = hour;
+ dateTime->minute = minute;
+ dateTime->second = second;
+ dateTime->hasDate = hasDate;
+ dateTime->hasTime = hasTime;
+ dateTime->hasTimeZone = hasTimeZone;
+ dateTime->tzSign = tzSign;
+ dateTime->tzHour = tzHour;
+ dateTime->tzMinute = tzMinute;
+ dateTime->nanoSecond = nanoSecond;
+
+} // FillDateTime
+
+// -------------------------------------------------------------------------------------------------
+
+static void WriteMajorLabel ( FILE * log, const char * title )
+{
+
+ fprintf ( log, "\n" );
+ fprintf ( log, "// =============================================================================\n" );
+ fprintf ( log, "// %s.\n", title );
+ fprintf ( log, "// =============================================================================\n" );
+ fflush ( log );
+
+} // WriteMajorLabel
+
+// -------------------------------------------------------------------------------------------------
+
+static void WriteMinorLabel ( FILE * log, const char * title )
+{
+
+ fprintf ( log, "\n// " );
+ for ( size_t i = 0; i < strlen(title); ++i ) fprintf ( log, "-" );
+ fprintf ( log, "--\n// %s :\n\n", title );
+ fflush ( log );
+
+} // WriteMinorLabel
+
+// -------------------------------------------------------------------------------------------------
+
+static XMP_Status DumpToString ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ XMP_Status status = 0;
+ std::string * dumpString = (std::string*)refCon;
+
+ try {
+ dumpString->append ( outStr, outLen );
+ } catch ( ... ) {
+ status = -1;
+ }
+ return status;
+
+} // DumpToString
+
+// -------------------------------------------------------------------------------------------------
+
+static XMP_Status DumpToFile ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ XMP_Status status = 0;
+ size_t count;
+ FILE * outFile = static_cast < FILE * > ( refCon );
+
+ count = fwrite ( outStr, 1, outLen, outFile );
+ if ( count != outLen ) status = errno;
+ fflush ( outFile );
+ return status;
+
+} // DumpToFile
+
+// -------------------------------------------------------------------------------------------------
+
+static void DumpXMPObj ( FILE * log, SXMPMeta & meta, const char * title )
+{
+
+ WriteMinorLabel ( log, title );
+ meta.DumpObject ( DumpToFile, log );
+
+} // DumpXMPObj
+
+// -------------------------------------------------------------------------------------------------
+
+static void VerifyNewlines ( FILE * log, std::string xmp, const char * newline )
+{
+ for ( size_t i = 0; i < xmp.size(); ++i ) {
+ if ( (xmp[i] == '\x0A') || (xmp[i] == '\x0D') ) {
+ if ( strncmp ( &xmp[i], newline, strlen(newline) ) != 0 ) {
+ fprintf ( log, "** Wrong newline at offset %zd\n", i );
+ }
+ if ( strlen(newline) == 2 ) ++i;
+ }
+ }
+}
+
+// =================================================================================================
+
+static void DoXMPCoreCoverage ( FILE * log )
+{
+
+ int i;
+ bool ok;
+ std::string tmpStr1, tmpStr2, tmpStr3, tmpStr4;
+ XMP_OptionBits options;
+
+ #if 0
+ { // Use this to be able to move the app window away from debugger windows.
+ string junk;
+ cout << "Move window, type anything to continue";
+ cin >> junk;
+ }
+ #endif
+
+ // --------------------------------------------------------------------------------------------
+
+ #if 0
+
+ WriteMajorLabel ( log, "Test global XMP toolkit options" );
+
+ fprintf ( log, "Initial global options 0x%X\n", SXMPMeta::GetGlobalOptions() );
+ SXMPMeta::SetGlobalOptions ( 0 );
+ fprintf ( log, "Final global options 0x%X\n", SXMPMeta::GetGlobalOptions() );
+
+ #endif
+
+ // --------------------------------------------------------------------------------------------
+
+ WriteMajorLabel ( log, "Dump predefined namespaces" );
+
+ SXMPMeta::DumpNamespaces ( DumpToFile, log );
+
+ // --------------------------------------------------------------------------------------------
+
+ {
+ WriteMajorLabel ( log, "Test simple constructors and parsing, setting the instance ID" );
+
+ SXMPMeta meta1;
+ DumpXMPObj ( log, meta1, "Empty XMP object" );
+ meta1.GetObjectName ( &tmpStr1 );
+ fprintf ( log, "\nEmpty object name = \"%s\"\n", tmpStr1.c_str() );
+ meta1.SetObjectName ( "New object name" );
+ DumpXMPObj ( log, meta1, "Set object name" );
+
+ SXMPMeta meta2 ( kRDFCoverage, strlen ( kRDFCoverage ) );
+ DumpXMPObj ( log, meta2, "Construct and parse from buffer" );
+ meta2.GetObjectName ( &tmpStr1 );
+ fprintf ( log, "\nRDFCoverage object name = \"%s\"\n", tmpStr1.c_str() );
+
+ meta2.SetProperty ( kXMP_NS_XMP_MM, "InstanceID", "meta2:Original" );
+ DumpXMPObj ( log, meta2, "Add instance ID" );
+
+ SXMPMeta meta4;
+ meta4 = meta2.Clone();
+ meta4.SetProperty ( kXMP_NS_XMP_MM, "InstanceID", "meta4:Clone" );
+ DumpXMPObj ( log, meta4, "Clone and add instance ID" );
+
+ #if 0
+
+ WriteMajorLabel ( log, "Test XMPMeta object options" );
+
+ fprintf ( log, "Initial object options 0x%X\n", meta2.GetObjectOptions() );
+ meta2.SetObjectOptions ( <TBD> );
+ fprintf ( log, "Final object options 0x%X\n", meta2.GetObjectOptions() );
+
+ #endif
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Static namespace functions
+ // --------------------------
+
+ WriteMajorLabel ( log, "Test static namespace functions" );
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ ok = SXMPMeta::RegisterNamespace ( kNS2, "ns2", &tmpStr1 );
+ fprintf ( log, "RegisterNamespace ns2 : %s, %s\n", YesOrNo ( ok ), tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ ok = SXMPMeta::RegisterNamespace ( kNS2, "nsx:", &tmpStr1 );
+ fprintf ( log, "RegisterNamespace nsx : %s, %s\n", YesOrNo ( ok ), tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ ok = SXMPMeta::GetNamespacePrefix ( kNS1, &tmpStr1 );
+ fprintf ( log, "GetNamespacePrefix ns1 : %s, %s\n", FoundOrNot ( ok ), tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ ok = SXMPMeta::GetNamespaceURI ( "ns1", &tmpStr1 );
+ fprintf ( log, "GetNamespaceURI ns1 : %s, %s\n", FoundOrNot ( ok ), tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ ok = SXMPMeta::GetNamespacePrefix ( "bogus", &tmpStr1 );
+ fprintf ( log, "GetNamespacePrefix bogus : %s\n", FoundOrNot ( ok ) );
+
+ tmpStr1.erase();
+ ok = SXMPMeta::GetNamespaceURI ( "bogus", &tmpStr1 );
+ fprintf ( log, "GetNamespaceURI bogus : %s\n", FoundOrNot ( ok ) );
+
+ SXMPMeta::DumpNamespaces ( DumpToFile, log );
+
+ #if 0
+ SXMPMeta::DeleteNamespace ( kNS2 );
+ SXMPMeta::DumpNamespaces ( DumpToFile, log );
+ (void) SXMPMeta::RegisterNamespace ( kNS2, "ns2", 0 );
+ #endif
+
+ // --------------------------------------------------------------------------------------------
+ // Basic set/get methods
+ // ---------------------
+
+ {
+ SXMPMeta meta;
+
+ WriteMajorLabel ( log, "Test SetProperty and related methods" );
+
+ tmpStr1 = "Prop value";
+ meta.SetProperty ( kNS1, "Prop", tmpStr1 );
+ meta.SetProperty ( kNS1, "ns1:XMLProp", "<PropValue/>" );
+ meta.SetProperty ( kNS1, "ns1:URIProp", "URI:value/", kXMP_PropValueIsURI );
+
+ tmpStr1 = "BagItem value";
+ meta.AppendArrayItem ( kNS1, "Bag", kXMP_PropValueIsArray, tmpStr1 );
+ meta.AppendArrayItem ( kNS1, "ns1:Seq", kXMP_PropArrayIsOrdered, "SeqItem value" );
+ meta.AppendArrayItem ( kNS1, "ns1:Alt", kXMP_PropArrayIsAlternate, "AltItem value" );
+
+ tmpStr1 = "Field1 value";
+ meta.SetStructField ( kNS1, "Struct", kNS2, "Field1", tmpStr1 );
+ meta.SetStructField ( kNS1, "ns1:Struct", kNS2, "Field2", "Field2 value" );
+ meta.SetStructField ( kNS1, "ns1:Struct", kNS2, "Field3", "Field3 value" );
+
+ tmpStr1 = "BagItem 3";
+ meta.SetArrayItem ( kNS1, "Bag", 1, tmpStr1 );
+ meta.SetArrayItem ( kNS1, "ns1:Bag", 1, "BagItem 1", kXMP_InsertBeforeItem );
+ meta.SetArrayItem ( kNS1, "ns1:Bag", 1, "BagItem 2", kXMP_InsertAfterItem );
+ meta.AppendArrayItem ( kNS1, "Bag", 0, "BagItem 4" );
+
+ DumpXMPObj ( log, meta, "A few basic Set... calls" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper );
+ fprintf ( log, "\n%s\n", tmpStr1.c_str() );
+
+ fprintf ( log, "CountArrayItems Bag = %d\n", meta.CountArrayItems ( kNS1, "Bag" ) );
+
+ meta.SetProperty ( kNS1, "QualProp1", "Prop value" );
+ meta.SetQualifier ( kNS1, "QualProp1", kNS2, "Qual1", "Qual1 value" );
+ // *** meta.SetProperty ( kNS1, "QualProp1/Qual2", "Qual2 value", kXMP_PropIsQualifier ); invalid
+ meta.SetProperty ( kNS1, "QualProp1/?ns2:Qual3", "Qual3 value" );
+ meta.SetProperty ( kNS1, "QualProp1/?xml:lang", "x-qual" );
+
+ meta.SetProperty ( kNS1, "QualProp2", "Prop value" );
+ meta.SetQualifier ( kNS1, "QualProp2", kXMP_NS_XML, "lang", "en-us" );
+ // *** meta.SetProperty ( kNS1, "QualProp2/xml:lang", "x-field", kXMP_PropIsQualifier ); invalid
+ meta.SetProperty ( kNS1, "QualProp2/@xml:lang", "x-attr" );
+
+ meta.SetProperty ( kNS1, "QualProp3", "Prop value" );
+ meta.SetQualifier ( kNS1, "ns1:QualProp3", kXMP_NS_XML, "xml:lang", "en-us" );
+ meta.SetQualifier ( kNS1, "ns1:QualProp3", kNS2, "ns2:Qual", "Qual value" );
+
+ meta.SetProperty ( kNS1, "QualProp4", "Prop value" );
+ tmpStr1 = "Qual value";
+ meta.SetQualifier ( kNS1, "QualProp4", kNS2, "Qual", tmpStr1 );
+ meta.SetQualifier ( kNS1, "QualProp4", kXMP_NS_XML, "lang", "en-us" );
+
+ DumpXMPObj ( log, meta, "Add some qualifiers" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper );
+ fprintf ( log, "\n%s\n", tmpStr1.c_str() );
+
+ meta.SetProperty ( kNS1, "QualProp1", "new value" );
+ meta.SetProperty ( kNS1, "QualProp2", "new value" );
+ meta.SetProperty ( kNS1, "QualProp3", "new value" );
+ meta.SetProperty ( kNS1, "QualProp4", "new value" );
+ DumpXMPObj ( log, meta, "Change values and keep qualifiers" );
+
+ // ----------------------------------------------------------------------------------------
+
+ WriteMajorLabel ( log, "Test GetProperty and related methods" );
+
+ meta.DeleteProperty ( kNS1, "QualProp1" ); // ! Start with fresh qualifiers.
+ meta.DeleteProperty ( kNS1, "ns1:QualProp2" );
+ meta.DeleteProperty ( kNS1, "ns1:QualProp3" );
+ meta.DeleteProperty ( kNS1, "QualProp4" );
+
+ meta.SetProperty ( kNS1, "QualProp1", "Prop value" );
+ meta.SetQualifier ( kNS1, "QualProp1", kNS2, "Qual1", "Qual1 value" );
+
+ meta.SetProperty ( kNS1, "QualProp2", "Prop value" );
+ meta.SetQualifier ( kNS1, "QualProp2", kXMP_NS_XML, "lang", "en-us" );
+
+ meta.SetProperty ( kNS1, "QualProp3", "Prop value" );
+ meta.SetQualifier ( kNS1, "QualProp3", kXMP_NS_XML, "lang", "en-us" );
+ meta.SetQualifier ( kNS1, "QualProp3", kNS2, "Qual", "Qual value" );
+
+ meta.SetProperty ( kNS1, "QualProp4", "Prop value" );
+ meta.SetQualifier ( kNS1, "QualProp4", kNS2, "Qual", "Qual value" );
+ meta.SetQualifier ( kNS1, "QualProp4", kXMP_NS_XML, "lang", "en-us" );
+
+ DumpXMPObj ( log, meta, "XMP object" );
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ ok = meta.GetProperty ( kNS1, "Prop", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:Prop : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ try {
+ tmpStr1.erase();
+ ok = meta.GetProperty ( 0, "ns1:Prop", &tmpStr1, &options );
+ fprintf ( log, "#ERROR: No exception for GetProperty with no schema URI : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "GetProperty with no schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "GetProperty with no schema URI - threw unknown exception\n" );
+ }
+
+ tmpStr1.erase();
+ ok = meta.GetProperty ( kNS1, "ns1:XMLProp", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:XMLProp : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1.erase();
+ ok = meta.GetProperty ( kNS1, "ns1:URIProp", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:URIProp : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ ok = meta.GetArrayItem ( kNS1, "Bag", 2, &tmpStr1, &options );
+ fprintf ( log, "GetArrayItem ns1:Bag[2] : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ try {
+ tmpStr1.erase();
+ ok = meta.GetArrayItem ( 0, "ns1:Bag", 1, &tmpStr1, &options );
+ fprintf ( log, "#ERROR: No exception for GetArrayItem with no schema URI : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "GetArrayItem with no schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "GetArrayItem with no schema URI - threw unknown exception\n" );
+ }
+
+ tmpStr1.erase();
+ ok = meta.GetArrayItem ( kNS1, "ns1:Seq", 1, &tmpStr1, &options );
+ fprintf ( log, "GetArrayItem ns1:Seq[1] : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1.erase();
+ ok = meta.GetArrayItem ( kNS1, "ns1:Alt", kXMP_ArrayLastItem, &tmpStr1, &options );
+ fprintf ( log, "GetArrayItem ns1:Alt[1] : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ ok = meta.GetStructField ( kNS1, "Struct", kNS2, "Field1", &tmpStr1, &options );
+ fprintf ( log, "GetStructField ns1:Struct/ns2:Field1 : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1.erase();
+ ok = meta.GetStructField ( kNS1, "ns1:Struct", kNS2, "ns2:Field2", &tmpStr1, &options );
+ fprintf ( log, "GetStructField ns1:Struct/ns2:Field2 : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1.erase();
+ ok = meta.GetStructField ( kNS1, "ns1:Struct", kNS2, "ns2:Field3", &tmpStr1, &options );
+ fprintf ( log, "GetStructField ns1:Struct/ns2:Field3 : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1.erase();
+ ok = meta.GetQualifier ( kNS1, "QualProp1", kNS2, "Qual1", &tmpStr1, &options );
+ fprintf ( log, "GetQualifier ns1:QualProp1/?ns2:Qual1 : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ try {
+ tmpStr1.erase();
+ ok = meta.GetQualifier ( 0, "ns1:QualProp1", kNS2, "Qual1", &tmpStr1, &options );
+ fprintf ( log, "#ERROR: No exception for GetQualifier with no schema URI : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "GetQualifier with no schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "GetQualifier with no schema URI - threw unknown exception\n" );
+ }
+
+ tmpStr1.erase();
+ ok = meta.GetQualifier ( kNS1, "ns1:QualProp3", kXMP_NS_XML, "xml:lang", &tmpStr1, &options );
+ fprintf ( log, "GetQualifier ns1:QualProp3 : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1.erase();
+ ok = meta.GetQualifier ( kNS1, "ns1:QualProp3", kNS2, "ns2:Qual", &tmpStr1, &options );
+ fprintf ( log, "GetQualifier ns1:QualProp3/?ns2:Qual : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( kNS1, "Bag", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:Bag : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( kNS1, "Seq", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:Seq : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( kNS1, "Alt", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:Alt : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( kNS1, "Struct", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:Struct : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ fprintf ( log, "\n" );
+
+ try {
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( "ns:bogus/", "Bogus", &tmpStr1, &options );
+ fprintf ( log, "#ERROR: No exception for GetProperty with bogus schema URI: %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "GetProperty with bogus schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "GetProperty with bogus schema URI - threw unknown exception\n" );
+ }
+
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( kNS1, "Bogus", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:Bogus : %s\n", FoundOrNot ( ok ) );
+
+ tmpStr1 = "junk";
+ ok = meta.GetArrayItem ( kNS1, "Bag", 99, &tmpStr1, &options );
+ fprintf ( log, "GetArrayItem ns1:Bag[99] : %s\n", FoundOrNot ( ok ) );
+
+ tmpStr1 = "junk";
+ ok = meta.GetStructField ( kNS1, "Struct", kNS2, "Bogus", &tmpStr1, &options );
+ fprintf ( log, "GetStructField ns1:Struct/ns2:Bogus : %s\n", FoundOrNot ( ok ) );
+
+ tmpStr1 = "junk";
+ ok = meta.GetQualifier ( kNS1, "Prop", kNS2, "Bogus", &tmpStr1, &options );
+ fprintf ( log, "GetQualifier ns1:Prop/?ns2:Bogus : %s\n", FoundOrNot ( ok ) );
+
+ // ----------------------------------------------------------------------------------------
+
+ WriteMajorLabel ( log, "Test DoesPropertyExist, DeleteProperty, and related methods" );
+
+ DumpXMPObj ( log, meta, "XMP object" );
+ fprintf ( log, "\n" );
+
+ ok = meta.DoesPropertyExist ( kNS1, "Prop" );
+ fprintf ( log, "DoesPropertyExist ns1:Prop : %s\n", YesOrNo ( ok ) );
+
+ try {
+ ok = meta.DoesPropertyExist ( 0, "ns1:Bag" );
+ fprintf ( log, "#ERROR: No exception for DoesPropertyExist with no schema URI: %s\n", YesOrNo ( ok ) );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "DoesPropertyExist with no schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "DoesPropertyExist with no schema URI - threw unknown exception\n" );
+ }
+
+ ok = meta.DoesPropertyExist ( kNS1, "ns1:Struct" );
+ fprintf ( log, "DoesPropertyExist ns1:Struct : %s\n", YesOrNo ( ok ) );
+
+ fprintf ( log, "\n" );
+
+ ok = meta.DoesArrayItemExist ( kNS1, "Bag", 2 );
+ fprintf ( log, "DoesArrayItemExist ns1:Bag[2] : %s\n", YesOrNo ( ok ) );
+
+ ok = meta.DoesArrayItemExist ( kNS1, "ns1:Seq", kXMP_ArrayLastItem );
+ fprintf ( log, "DoesArrayItemExist ns1:Seq[last] : %s\n", YesOrNo ( ok ) );
+
+ ok = meta.DoesStructFieldExist ( kNS1, "Struct", kNS2, "Field1" );
+ fprintf ( log, "DoesStructFieldExist ns1:Struct/ns2:Field1 : %s\n", YesOrNo ( ok ) );
+
+ ok = meta.DoesQualifierExist ( kNS1, "QualProp1", kNS2, "Qual1" );
+ fprintf ( log, "DoesQualifierExist ns1:QualProp1/?ns2:Qual1 : %s\n", YesOrNo ( ok ) );
+
+ ok = meta.DoesQualifierExist ( kNS1, "QualProp2", kXMP_NS_XML, "lang" );
+ fprintf ( log, "DoesQualifierExist ns1:QualProp2/?xml:lang : %s\n", YesOrNo ( ok ) );
+
+ fprintf ( log, "\n" );
+
+ try {
+ ok = meta.DoesPropertyExist ( "ns:bogus/", "Bogus" );
+ fprintf ( log, "#ERROR: No exception for DoesPropertyExist with bogus schema URI: %s\n", YesOrNo ( ok ) );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "DoesPropertyExist with bogus schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "DoesPropertyExist with bogus schema URI - threw unknown exception\n" );
+ }
+
+ ok = meta.DoesPropertyExist ( kNS1, "Bogus" );
+ fprintf ( log, "DoesPropertyExist ns1:Bogus : %s\n", YesOrNo ( ok ) );
+
+ ok = meta.DoesArrayItemExist ( kNS1, "Bag", 99 );
+ fprintf ( log, "DoesArrayItemExist ns1:Bag[99] : %s\n", YesOrNo ( ok ) );
+
+ try {
+ ok = meta.DoesArrayItemExist ( 0, "ns1:Bag", kXMP_ArrayLastItem );
+ fprintf ( log, "#ERROR: No exception for DoesArrayItemExist with no schema URI: %s\n", YesOrNo ( ok ) );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "DoesArrayItemExist with no schema URI - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "DoesArrayItemExist with no schema URI - threw unknown exception\n" );
+ }
+
+ ok = meta.DoesStructFieldExist ( kNS1, "Struct", kNS2, "Bogus" );
+ fprintf ( log, "DoesStructFieldExist ns1:Struct/ns2:Bogus : %s\n", YesOrNo ( ok ) );
+
+ ok = meta.DoesQualifierExist ( kNS1, "Prop", kNS2, "Bogus" );
+ fprintf ( log, "DoesQualifierExist ns1:Prop/?ns2:Bogus : %s\n", YesOrNo ( ok ) );
+
+ meta.DeleteProperty ( kNS1, "Prop" );
+ meta.DeleteArrayItem ( kNS1, "Bag", 2 );
+ meta.DeleteStructField ( kNS1, "Struct", kNS2, "Field1" );
+
+ DumpXMPObj ( log, meta, "Delete Prop, Bag[2], and Struct1/Field1" );
+
+ meta.DeleteQualifier ( kNS1, "QualProp1", kNS2, "Qual1" );
+ meta.DeleteQualifier ( kNS1, "QualProp2", kXMP_NS_XML, "lang" );
+ meta.DeleteQualifier ( kNS1, "QualProp3", kNS2, "Qual" );
+ meta.DeleteQualifier ( kNS1, "QualProp4", kXMP_NS_XML, "lang" );
+
+ DumpXMPObj ( log, meta, "Delete QualProp1/?ns2:Qual1, QualProp2/?xml:lang, QualProp3:/ns2:Qual, and QualProp4/?xml:lang" );
+
+ meta.DeleteProperty ( kNS1, "Bag" );
+ meta.DeleteProperty ( kNS1, "Struct" );
+
+ DumpXMPObj ( log, meta, "Delete all of Bag and Struct" );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Localized text set/get methods
+ // ------------------------------
+
+ {
+ SXMPMeta meta;
+
+ WriteMajorLabel ( log, "Test SetLocalizedText and GetLocalizedText" );
+
+ tmpStr1 = "default value";
+ meta.SetLocalizedText ( kNS1, "AltText", "", "x-default", tmpStr1 );
+ DumpXMPObj ( log, meta, "Set x-default value" );
+
+ meta.SetLocalizedText ( kNS1, "AltText", "en", "en-us", "en-us value" );
+ DumpXMPObj ( log, meta, "Set en/en-us value" );
+
+ meta.SetLocalizedText ( kNS1, "AltText", "en", "en-uk", "en-uk value" );
+ DumpXMPObj ( log, meta, "Set en/en-uk value" );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase(); tmpStr2.erase();
+ ok = meta.GetLocalizedText ( kNS1, "AltText", "en", "en-ca", &tmpStr1, &tmpStr2, &options );
+ fprintf ( log, "GetLocalizedText en/en-ca : %s, \'%s\' \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), tmpStr2.c_str(), options );
+
+ tmpStr1 = "junk";
+ ok = meta.GetProperty ( kNS1, "AltText", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ns1:AltText : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Binary value set/get methods
+ // ----------------------------
+
+ {
+ SXMPMeta meta ( kDateTimeRDF, strlen(kDateTimeRDF) );
+ XMP_DateTime dateValue;
+ bool boolValue;
+ XMP_Int32 intValue;
+ double floatValue;
+ char dateName [8];
+
+ WriteMajorLabel ( log, "Test SetProperty... and GetProperty... methods (set/get with binary values)" );
+
+ FillDateTime ( &dateValue, 2000, 1, 2, 3, 4, 5, true, true, false, 0, 0, 0, 0 );
+
+ meta.SetProperty_Bool ( kNS1, "Bool0", false );
+ meta.SetProperty_Bool ( kNS1, "Bool1", true );
+ meta.SetProperty_Int ( kNS1, "Int", 42 );
+ meta.SetProperty_Float ( kNS1, "Float", 4.2 );
+
+ meta.SetProperty_Date ( kNS1, "Date10", dateValue );
+ dateValue.tzSign = 1; dateValue.tzHour = 6; dateValue.tzMinute = 7;
+ meta.SetProperty_Date ( kNS1, "Date11", dateValue );
+ dateValue.tzSign = -1;
+ meta.SetProperty_Date ( kNS1, "Date12", dateValue );
+ dateValue.nanoSecond = 9;
+ meta.SetProperty_Date ( kNS1, "Date13", dateValue );
+
+ DumpXMPObj ( log, meta, "A few basic binary Set... calls" );
+
+ fprintf ( log, "\n" );
+
+ ok = meta.GetProperty_Bool ( kNS1, "Bool0", &boolValue, &options );
+ fprintf ( log, "GetProperty_Bool Bool0 : %s, %d, 0x%X\n", FoundOrNot ( ok ), boolValue, options );
+
+ ok = meta.GetProperty_Bool ( kNS1, "Bool1", &boolValue, &options );
+ fprintf ( log, "GetProperty_Bool Bool1 : %s, %d, 0x%X\n", FoundOrNot ( ok ), boolValue, options );
+
+ ok = meta.GetProperty_Int ( kNS1, "Int", &intValue, &options );
+ fprintf ( log, "GetProperty_Int : %s, %d, 0x%X\n", FoundOrNot ( ok ), intValue, options );
+
+ ok = meta.GetProperty_Float ( kNS1, "Float", &floatValue, &options );
+ fprintf ( log, "GetProperty_Float : %s, %f, 0x%X\n", FoundOrNot ( ok ), floatValue, options );
+
+ fprintf ( log, "\n" );
+
+ for ( i = 1; i < 14; ++i ) {
+ sprintf ( dateName, "Date%d", i );
+ ok = meta.GetProperty_Date ( kNS1, dateName, &dateValue, &options );
+ fprintf ( log, "GetProperty_Date (%s) : %s, %d-%02d-%02d %02d:%02d:%02d %d*%02d:%02d %d, 0x%X\n", dateName, FoundOrNot ( ok ),
+ dateValue.year, dateValue.month, dateValue.day, dateValue.hour, dateValue.minute, dateValue.second,
+ dateValue.tzSign, dateValue.tzHour, dateValue.tzMinute, dateValue.nanoSecond, options );
+ meta.SetProperty_Date ( kNS2, dateName, dateValue );
+ }
+
+ DumpXMPObj ( log, meta, "Get and re-set the dates" );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Parse and serialize methods
+ // ---------------------------
+
+ WriteMajorLabel ( log, "Test parsing with multiple buffers and various options" );
+
+ {
+ SXMPMeta meta;
+ for ( i = 0; i < (long)strlen(kSimpleRDF) - 10; i += 10 ) {
+ meta.ParseFromBuffer ( &kSimpleRDF[i], 10, kXMP_ParseMoreBuffers );
+ }
+ meta.ParseFromBuffer ( &kSimpleRDF[i], strlen(kSimpleRDF) - i );
+ DumpXMPObj ( log, meta, "Multiple buffer parse" );
+ }
+
+ {
+ SXMPMeta meta;
+ for ( i = 0; i < (long)strlen(kSimpleRDF) - 10; i += 10 ) {
+ meta.ParseFromBuffer ( &kSimpleRDF[i], 10, kXMP_ParseMoreBuffers );
+ }
+ meta.ParseFromBuffer ( &kSimpleRDF[i], (strlen(kSimpleRDF) - i), kXMP_ParseMoreBuffers );
+ meta.ParseFromBuffer ( kSimpleRDF, 0 );
+ DumpXMPObj ( log, meta, "Multiple buffer parse, empty last buffer" );
+ }
+
+ {
+ SXMPMeta meta;
+ for ( i = 0; i < (long)strlen(kSimpleRDF) - 10; i += 10 ) {
+ meta.ParseFromBuffer ( &kSimpleRDF[i], 10, kXMP_ParseMoreBuffers );
+ }
+ meta.ParseFromBuffer ( &kSimpleRDF[i], (strlen(kSimpleRDF) - i), kXMP_ParseMoreBuffers );
+ meta.ParseFromBuffer ( 0, 0 );
+ DumpXMPObj ( log, meta, "Multiple buffer parse, null last buffer" );
+ }
+
+ {
+ SXMPMeta meta;
+ meta.ParseFromBuffer ( kSimpleRDF, strlen(kSimpleRDF), kXMP_RequireXMPMeta );
+ DumpXMPObj ( log, meta, "Parse and require xmpmeta element, which is missing" );
+ }
+
+ {
+ SXMPMeta meta;
+ meta.ParseFromBuffer ( kNamespaceRDF, strlen(kNamespaceRDF) );
+ DumpXMPObj ( log, meta, "Parse RDF with multiple nested namespaces" );
+ }
+
+ {
+ SXMPMeta meta;
+ meta.ParseFromBuffer ( kXMPMetaRDF, strlen(kXMPMetaRDF), kXMP_RequireXMPMeta );
+ DumpXMPObj ( log, meta, "Parse and require xmpmeta element, which is present" );
+ }
+
+ {
+ SXMPMeta meta;
+ meta.ParseFromBuffer ( kInconsistentRDF, strlen(kInconsistentRDF) );
+ DumpXMPObj ( log, meta, "Parse and reconcile inconsistent aliases" );
+ }
+
+ try {
+ SXMPMeta meta;
+ meta.ParseFromBuffer ( kInconsistentRDF, strlen(kInconsistentRDF), kXMP_StrictAliasing );
+ DumpXMPObj ( log, meta, "ERROR: Parse and do not reconcile inconsistent aliases - should have thrown an exception" );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "\nParse and do not reconcile inconsistent aliases - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "\nParse and do not reconcile inconsistent aliases - threw unknown exception\n" );
+ }
+
+ {
+ WriteMajorLabel ( log, "Test CR and LF in values" );
+
+ const char * kValueWithCR = "ASCII \x0D CR";
+ const char * kValueWithLF = "ASCII \x0A LF";
+ const char * kValueWithCRLF = "ASCII \x0D\x0A CRLF";
+
+ SXMPMeta meta ( kNewlineRDF, kXMP_UseNullTermination );
+
+ meta.SetProperty ( kNS2, "HasCR", kValueWithCR );
+ meta.SetProperty ( kNS2, "HasLF", kValueWithLF );
+ meta.SetProperty ( kNS2, "HasCRLF", kValueWithCRLF );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper );
+ fprintf ( log, "\n%s\n", tmpStr1.c_str() );
+
+ tmpStr1.erase(); tmpStr2.erase();
+ ok = meta.GetProperty ( kNS1, "HasCR", &tmpStr1, 0 );
+ ok = meta.GetProperty ( kNS2, "HasCR", &tmpStr2, 0 );
+ if ( (tmpStr1 != kValueWithCR) || (tmpStr2 != kValueWithCR) ) fprintf ( log, "\n ## HasCR values are bad\n" );
+
+ tmpStr1.erase(); tmpStr2.erase();
+ ok = meta.GetProperty ( kNS1, "HasLF", &tmpStr1, 0 );
+ ok = meta.GetProperty ( kNS2, "HasLF", &tmpStr2, 0 );
+ if ( (tmpStr1 != kValueWithLF) || (tmpStr2 != kValueWithLF) ) fprintf ( log, "\n ## HasLF values are bad\n" );
+
+ tmpStr1.erase(); tmpStr2.erase();
+ ok = meta.GetProperty ( kNS1, "HasCRLF", &tmpStr1, 0 );
+ ok = meta.GetProperty ( kNS2, "HasCRLF", &tmpStr2, 0 );
+ if ( (tmpStr1 != kValueWithCRLF) || (tmpStr2 != kValueWithCRLF) ) fprintf ( log, "\n ## HasCRLF values are bad\n" );
+ }
+
+ {
+ WriteMajorLabel ( log, "Test serialization with various options" );
+
+ SXMPMeta meta ( kSimpleRDF, strlen(kSimpleRDF) );
+ meta.SetProperty ( kNS2, "Another", "Something in another schema" );
+ meta.SetProperty ( kNS2, "Yet/pdf:More", "Yet more in another schema" );
+
+ DumpXMPObj ( log, meta, "Parse simple RDF, serialize with various options" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1 );
+ WriteMinorLabel ( log, "Default serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() ); fflush ( log );
+ VerifyNewlines ( log, tmpStr1, "\x0A" );
+
+ SXMPMeta meta2 ( tmpStr1.c_str(), tmpStr1.size() );
+ DumpXMPObj ( log, meta2, "Reparse default serialization" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper | kXMP_UseCompactFormat );
+ WriteMinorLabel ( log, "Compact RDF, no packet serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+
+ SXMPMeta meta3 ( tmpStr1.c_str(), tmpStr1.size() );
+ DumpXMPObj ( log, meta3, "Reparse compact serialization" );
+
+ {
+ SXMPMeta meta2;
+
+ meta2.SetProperty ( kXMP_NS_PDF, "Author", "PDF Author" );
+
+ tmpStr1.erase();
+ meta2.SerializeToBuffer ( &tmpStr1, kXMP_ReadOnlyPacket );
+ WriteMinorLabel ( log, "Read-only serialize with alias comments" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+
+ meta2.SetProperty ( kXMP_NS_PDF, "Actual", "PDF Actual" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Actual", "XMP Actual" );
+
+ tmpStr1.erase();
+ meta2.SerializeToBuffer ( &tmpStr1, kXMP_ReadOnlyPacket );
+ WriteMinorLabel ( log, "Read-only serialize with alias comments (more actuals)" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+ }
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper, 0, "\x0D" );
+ WriteMinorLabel ( log, "CR newline serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+ VerifyNewlines ( log, tmpStr1, "\x0D" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper, 0, "\x0D\x0A" );
+ WriteMinorLabel ( log, "CRLF newline serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+ VerifyNewlines ( log, tmpStr1, "\x0D\x0A" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper, 0, "<->" );
+ WriteMinorLabel ( log, "Alternate newline serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_OmitPacketWrapper, 0, "", "#", 3 );
+ WriteMinorLabel ( log, "Alternate indent serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, 0, 10 );
+ WriteMinorLabel ( log, "Small padding serialize" );
+ fprintf ( log, "%s\n", tmpStr1.c_str() );
+
+ tmpStr1.erase();
+ tmpStr2.erase();
+ meta.SerializeToBuffer ( &tmpStr1 );
+ meta.SerializeToBuffer ( &tmpStr2, kXMP_IncludeThumbnailPad );
+ fprintf ( log, "Thumbnailpad adds %zd bytes\n", tmpStr2.size()-tmpStr1.size() );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_ReadOnlyPacket );
+ size_t minSize = tmpStr1.size();
+ fprintf ( log, "Minimum packet size is %zd bytes\n", minSize );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_ExactPacketLength, minSize+1234 );
+ fprintf ( log, "Minimum+1234 packet size is %zd bytes\n", tmpStr1.size() );
+ if ( tmpStr1.size() != (minSize + 1234) ) fprintf ( log, "** Bad packet length **\n" );
+
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_ExactPacketLength, minSize );
+ fprintf ( log, "Minimum+0 packet size is %zd bytes\n", tmpStr1.size() );
+ if ( tmpStr1.size() != minSize ) fprintf ( log, "** Bad packet length **\n" );
+
+ try {
+ tmpStr1.erase();
+ meta.SerializeToBuffer ( &tmpStr1, kXMP_ExactPacketLength, minSize-1 );
+ fprintf ( log, "#ERROR: No exception for minimum-1, size is %zd bytes **\n", tmpStr1.size() );
+ } catch ( XMP_Error & excep ) {
+ fprintf ( log, "Serialize in minimum-1 - threw XMP_Error #%d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ } catch ( ... ) {
+ fprintf ( log, "Serialize in minimum-1 - threw unknown exception\n" );
+ }
+
+ // *** UTF-16 and UTF-32 encodings
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Iteration methods
+ // -----------------
+
+ {
+ WriteMajorLabel ( log, "Test iteration methods" );
+
+ SXMPMeta meta ( kRDFCoverage, strlen ( kRDFCoverage ) );
+ XMP_OptionBits opt2;
+
+ meta.SetProperty ( kNS2, "Prop", "Prop value" );
+
+ meta.SetProperty ( kNS2, "Bag", 0, kXMP_PropValueIsArray );
+ meta.SetArrayItem ( kNS2, "Bag", 1, "BagItem 2" );
+ meta.SetArrayItem ( kNS2, "Bag", 1, "BagItem 1", kXMP_InsertBeforeItem );
+ meta.SetArrayItem ( kNS2, "Bag", 2, "BagItem 3", kXMP_InsertAfterItem );
+
+ DumpXMPObj ( log, meta, "Parse \"coverage\" RDF, add Bag items out of order" );
+
+ {
+ SXMPIterator iter ( meta );
+ WriteMinorLabel ( log, "Default iteration" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kXMP_IterOmitQualifiers );
+ WriteMinorLabel ( log, "Iterate omitting qualifiers" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kXMP_IterJustLeafName );
+ WriteMinorLabel ( log, "Iterate with just leaf names" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kXMP_IterJustLeafNodes );
+ WriteMinorLabel ( log, "Iterate just the leaf nodes" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kXMP_IterJustChildren );
+ WriteMinorLabel ( log, "Iterate just the schema nodes" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2 );
+ WriteMinorLabel ( log, "Iterate the ns2: namespace" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "Bag" );
+ WriteMinorLabel ( log, "Start at ns2:Bag" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "NestedStructProp/ns1:Outer" );
+ WriteMinorLabel ( log, "Start at ns2:NestedStructProp/ns1:Outer" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, "ns:empty/" );
+ WriteMinorLabel ( log, "Iterate an empty namespace" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "", kXMP_IterJustChildren | kXMP_IterJustLeafName );
+ WriteMinorLabel ( log, "Iterate the top of the ns2: namespace with just leaf names" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "", kXMP_IterJustChildren | kXMP_IterJustLeafNodes );
+ WriteMinorLabel ( log, "Iterate the top of the ns2: namespace visiting just leaf nodes" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "Bag", kXMP_IterJustChildren );
+ WriteMinorLabel ( log, "Iterate just the children of ns2:Bag" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "Bag", kXMP_IterJustChildren | kXMP_IterJustLeafName );
+ WriteMinorLabel ( log, "Iterate just the children of ns2:Bag with just leaf names" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta, kNS2, "NestedStructProp/ns1:Outer/ns1:Middle", kXMP_IterJustChildren );
+ WriteMinorLabel ( log, "Iterate just the children of ns2:NestedStructProp/ns1:Outer/ns1:Middle" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ {
+ SXMPIterator iter ( meta );
+ WriteMinorLabel ( log, "Skip children of ArrayProp2, and siblings after StructProp" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ if ( tmpStr2 == "ns1:ArrayProp2" ) iter.Skip ( kXMP_IterSkipSubtree );
+ if ( tmpStr2 == "ns1:StructProp" ) iter.Skip ( kXMP_IterSkipSiblings );
+ }
+ }
+
+ {
+ SXMPMeta meta;
+
+ meta.SetProperty ( kXMP_NS_PDF, "Author", "PDF Author" );
+ meta.SetProperty ( kXMP_NS_PDF, "PDFProp", "PDF Prop" );
+ meta.SetProperty ( kXMP_NS_XMP, "XMPProp", "XMP Prop" );
+ meta.SetProperty ( kXMP_NS_DC, "DCProp", "DC Prop" );
+
+ SXMPIterator iter1 ( meta );
+ WriteMinorLabel ( log, "Iterate without showing aliases" );
+ while ( true ) {
+ tmpStr1.erase(); tmpStr2.erase(); tmpStr3.erase();
+ if ( ! iter1.Next ( &tmpStr1, &tmpStr2, &tmpStr3, &options ) ) break;
+ fprintf ( log, " %s %s = \"%s\", 0x%X\n", tmpStr1.c_str(), tmpStr2.c_str(), tmpStr3.c_str(), options );
+ if ( ! (options & kXMP_SchemaNode) ) {
+ tmpStr4.erase();
+ options &= kXMP_PropHasAliases; // So the comparison below works.
+ ok = meta.GetProperty ( tmpStr1.c_str(), tmpStr2.c_str(), &tmpStr4, &opt2 );
+ if ( (! ok) || (tmpStr4 != tmpStr3) || (opt2 != options) ) {
+ fprintf ( log, " ** GetProperty failed: %s, \"%s\", 0x%X\n", FoundOrNot(ok), tmpStr4.c_str(), opt2 );
+ }
+ }
+ }
+ }
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // XPath composition utilities
+ // ---------------------------
+
+ {
+ WriteMajorLabel ( log, "Test XPath composition utilities" );
+
+ SXMPMeta meta ( kSimpleRDF, strlen(kSimpleRDF) );
+ DumpXMPObj ( log, meta, "Parse simple RDF" );
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::ComposeArrayItemPath ( kNS1, "ArrayProp", 2, &tmpStr1 );
+ fprintf ( log, "ComposeArrayItemPath ns1:ArrayProp[2] : %s\n", tmpStr1.c_str() );
+ meta.SetProperty ( kNS1, tmpStr1.c_str(), "new ns1:ArrayProp[2] value" );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::ComposeStructFieldPath ( kNS1, "StructProp", kNS2, "Field3", &tmpStr1 );
+ fprintf ( log, "ComposeStructFieldPath ns1:StructProp/ns2:Field3 : %s\n", tmpStr1.c_str() );
+ meta.SetProperty ( kNS1, tmpStr1.c_str(), "new ns1:StructProp/ns2:Field3 value" );
+
+ tmpStr1.erase();
+ SXMPUtils::ComposeQualifierPath ( kNS1, "QualProp", kNS2, "Qual", &tmpStr1 );
+ fprintf ( log, "ComposeQualifierPath ns1:QualProp/?ns2:Qual : %s\n", tmpStr1.c_str() );
+ meta.SetProperty ( kNS1, tmpStr1.c_str(), "new ns1:QualProp/?ns2:Qual value" );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::ComposeQualifierPath ( kNS1, "AltTextProp", kXMP_NS_XML, "lang", &tmpStr1 );
+ fprintf ( log, "ComposeQualifierPath ns1:AltTextProp/?xml:lang : %s\n", tmpStr1.c_str() );
+ meta.SetProperty ( kNS1, tmpStr1.c_str(), "new ns1:AltTextProp/?xml:lang value" );
+
+ tmpStr1.erase();
+ tmpStr2 = "x-two";
+ SXMPUtils::ComposeLangSelector ( kNS1, "AltTextProp", tmpStr2, &tmpStr1 );
+ fprintf ( log, "ComposeLangSelector ns1:AltTextProp['x-two'] : %s\n", tmpStr1.c_str() );
+ meta.SetProperty ( kNS1, tmpStr1.c_str(), "new ns1:AltTextProp['x-two'] value" );
+
+ fprintf ( log, "\n" );
+
+ fprintf ( log, "Check field selector usage\n" ); fflush ( log );
+
+ tmpStr1.erase();
+ ok = meta.GetProperty ( kNS1, "ArrayOfStructProp[ns2:Field1='Item-2']", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ArrayOfStructProp[ns2:Field1='Item-2'] : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options ); fflush ( log );
+
+ tmpStr1.erase();
+ ok = meta.GetProperty ( kNS1, "ArrayOfStructProp[ns2:Field1='Item-2']/ns2:Field2", &tmpStr1, &options );
+ fprintf ( log, "GetProperty ArrayOfStructProp[ns2:Field1='Item-2']/ns2:Field2 : %s, \"%s\", 0x%X\n", FoundOrNot ( ok ), tmpStr1.c_str(), options ); fflush ( log );
+
+ tmpStr1.erase();
+ tmpStr2 = "Item-2";
+ SXMPUtils::ComposeFieldSelector ( kNS1, "ArrayOfStructProp", kNS2, "Field1", tmpStr2, &tmpStr1 );
+ fprintf ( log, "ComposeFieldSelector ns1:ArrayOfStructProp[ns2:Field1=Item-2] : %s\n", tmpStr1.c_str() );
+
+ tmpStr2.erase();
+ SXMPUtils::ComposeStructFieldPath ( kNS1, tmpStr1.c_str(), kNS2, "Field2", &tmpStr2 );
+ fprintf ( log, "ComposeStructFieldPath ns1:ArrayOfStructProp[ns2:Field1=Item-2]/ns2:Field2 : %s\n", tmpStr2.c_str() );
+ meta.SetProperty ( kNS1, tmpStr2.c_str(), "new ns1:ArrayOfStructProp[ns2:Field1=Item-2]/ns2:Field2 value" );
+
+ DumpXMPObj ( log, meta, "Modified simple RDF" );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Value conversion utilities
+ // --------------------------
+
+ WriteMajorLabel ( log, "Test value conversion utilities" );
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromBool ( true, &tmpStr1 );
+ fprintf ( log, "ConverFromBool true : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromBool ( false, &tmpStr1 );
+ fprintf ( log, "ConverFromBool false : %s\n", tmpStr1.c_str() );
+
+ fprintf ( log, "\n" );
+
+ ok = SXMPUtils::ConvertToBool ( kXMP_TrueStr );
+ fprintf ( log, "ConverToBool kXMP_TrueStr : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( kXMP_FalseStr );
+ fprintf ( log, "ConverToBool kXMP_FalseStr : %d\n", (int)ok );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1 = "true";
+ ok = SXMPUtils::ConvertToBool ( tmpStr1 );
+ fprintf ( log, "ConverToBool true : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( "TRUE" );
+ fprintf ( log, "ConverToBool TRUE : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( "t" );
+ fprintf ( log, "ConverToBool t : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( "1" );
+ fprintf ( log, "ConverToBool 1 : %d\n", (int)ok );
+
+ fprintf ( log, "\n" );
+
+ ok = SXMPUtils::ConvertToBool ( "false" );
+ fprintf ( log, "ConverToBool false : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( "FALSE" );
+ fprintf ( log, "ConverToBool FALSE : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( "f" );
+ fprintf ( log, "ConverToBool f : %d\n", (int)ok );
+ ok = SXMPUtils::ConvertToBool ( "0" );
+ fprintf ( log, "ConverToBool 0 : %d\n", (int)ok );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( 0, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromInt 0 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( 42, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromInt 42 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( -42, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromInt -42 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( 0x7FFFFFFF, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromInt 0x7FFFFFFF : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( 0x80000000, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromInt 0x80000000 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( 0x7FFFFFFF, "%X", &tmpStr1 );
+ fprintf ( log, "ConverFromInt 0x7FFFFFFF as hex : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromInt ( 0x80000000, "%X", &tmpStr1 );
+ fprintf ( log, "ConverFromInt 0x80000000 as hex : %s\n", tmpStr1.c_str() );
+
+ fprintf ( log, "\n" );
+
+ long int1;
+
+ tmpStr1 = "0";
+ int1 = SXMPUtils::ConvertToInt ( tmpStr1 );
+ fprintf ( log, "ConvertToInt 0 : %ld\n", int1 );
+ int1 = SXMPUtils::ConvertToInt ( "42" );
+ fprintf ( log, "ConvertToInt 42 : %ld\n", int1 );
+ int1 = SXMPUtils::ConvertToInt ( "-42" );
+ fprintf ( log, "ConvertToInt -42 : %ld\n", int1 );
+ int1 = SXMPUtils::ConvertToInt ( "0x7FFFFFFF" );
+ fprintf ( log, "ConvertToInt 0x7FFFFFFF : %ld\n", int1 );
+ int1 = SXMPUtils::ConvertToInt ( "0x80000000" );
+ fprintf ( log, "ConvertToInt 0x80000000 : %ld\n", int1 );
+ int1 = SXMPUtils::ConvertToInt ( "0x7FFFFFFF" );
+ fprintf ( log, "ConvertToInt 0x7FFFFFFF as hex : %lX\n", int1 );
+ int1 = SXMPUtils::ConvertToInt ( "0x80000000" );
+ fprintf ( log, "ConvertToInt 0x80000000 as hex : %lX\n", int1 );
+
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( 0, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromFloat 0 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( 4.2, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromFloat 4.2 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( -4.2, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromFloat -4.2 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( (int)0x7FFFFFFF, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromFloat 0x7FFFFFFF : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( (int)0x80000000, 0, &tmpStr1 );
+ fprintf ( log, "ConverFromFloat 0x80000000 : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( (int)0x7FFFFFFF, "%f", &tmpStr1 );
+ fprintf ( log, "ConverFromFloat 0x7FFFFFFF as f : %s\n", tmpStr1.c_str() );
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromFloat ( (int)0x80000000, "%f", &tmpStr1 );
+ fprintf ( log, "ConverFromFloat 0x80000000 as f : %s\n", tmpStr1.c_str() );
+
+ fprintf ( log, "\n" );
+
+ double float1;
+
+ tmpStr1 = "0";
+ float1 = SXMPUtils::ConvertToFloat ( tmpStr1 );
+ fprintf ( log, "ConvertToFloat 0 : %f\n", float1 );
+ float1 = SXMPUtils::ConvertToFloat ( "4.2" );
+ fprintf ( log, "ConvertToFloat 4.2 : %f\n", float1 );
+ float1 = SXMPUtils::ConvertToFloat ( "-4.2" );
+ fprintf ( log, "ConvertToFloat -4.2 : %f\n", float1 );
+
+ fprintf ( log, "\n" );
+
+ XMP_DateTime date1, date2;
+ FillDateTime ( &date1, 2000, 1, 31, 12, 34, 56, true, true, true, -1, 8, 0, 0 );
+
+
+ tmpStr1.erase();
+ SXMPUtils::ConvertFromDate ( date1, &tmpStr1 );
+ fprintf ( log, "ConvertFromDate 2000 Jan 31 12:34:56 PST : %s\n", tmpStr1.c_str() );
+
+ SXMPUtils::ConvertToDate ( tmpStr1, &date2 );
+ fprintf ( log, "ConvertToDate : %d-%02d-%02d %02d:%02d:%02d %d*%02d:%02d %d\n",
+ date2.year, date2.month, date2.day, date2.hour, date2.minute, date2.second,
+ date2.tzSign, date2.tzHour, date2.tzMinute, date2.nanoSecond );
+
+ // --------------------------------------------------------------------------------------------
+ // Date/Time utilities
+ // -------------------
+
+ {
+ WriteMajorLabel ( log, "Test date/time utilities and special values" );
+ fprintf ( log, "\n" );
+
+ XMP_DateTime utcNow, localNow;
+
+ SXMPUtils::SetTimeZone ( &utcNow );
+ fprintf ( log, "SetTimeZone : %d-%02d-%02d %02d:%02d:%02d %d*%02d:%02d %d\n",
+ utcNow.year, utcNow.month, utcNow.day, utcNow.hour, utcNow.minute, utcNow.second,
+ utcNow.tzSign, utcNow.tzHour, utcNow.tzMinute, utcNow.nanoSecond );
+
+ SXMPUtils::CurrentDateTime ( &utcNow );
+ fprintf ( log, "CurrentDateTime : %d-%02d-%02d %02d:%02d:%02d %d*%02d:%02d %d\n",
+ utcNow.year, utcNow.month, utcNow.day, utcNow.hour, utcNow.minute, utcNow.second,
+ utcNow.tzSign, utcNow.tzHour, utcNow.tzMinute, utcNow.nanoSecond );
+
+ localNow = utcNow;
+ SXMPUtils::ConvertToLocalTime ( &localNow );
+ fprintf ( log, "ConvertToLocalTime : %d-%02d-%02d %02d:%02d:%02d %d*%02d:%02d %d\n",
+ localNow.year, localNow.month, localNow.day, localNow.hour, localNow.minute, localNow.second,
+ localNow.tzSign, localNow.tzHour, localNow.tzMinute, localNow.nanoSecond );
+
+ utcNow = localNow;
+ SXMPUtils::ConvertToUTCTime ( &utcNow );
+ fprintf ( log, "ConvertToUTCTime : %d-%02d-%02d %02d:%02d:%02d %d*%02d:%02d %d\n",
+ utcNow.year, utcNow.month, utcNow.day, utcNow.hour, utcNow.minute, utcNow.second,
+ utcNow.tzSign, utcNow.tzHour, utcNow.tzMinute, utcNow.nanoSecond );
+
+ fprintf ( log, "\n" );
+
+ i = SXMPUtils::CompareDateTime ( utcNow, localNow );
+ fprintf ( log, "CompareDateTime with a == b : %d\n", i );
+
+ utcNow.second = 0;
+ localNow.second = 30;
+ i = SXMPUtils::CompareDateTime ( utcNow, localNow );
+ fprintf ( log, "CompareDateTime with a < b : %d\n", i );
+
+ utcNow.second = 59;
+ i = SXMPUtils::CompareDateTime ( utcNow, localNow );
+ fprintf ( log, "CompareDateTime with a > b : %d\n", i );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+ // Miscellaneous utilities
+ // -----------------------
+
+ {
+ WriteMajorLabel ( log, "Test CatenateArrayItems and SeparateArrayItems" );
+ fprintf ( log, "\n" );
+
+ SXMPMeta meta;
+
+ meta.AppendArrayItem ( kNS1, "Array1", kXMP_PropValueIsArray, "one" );
+ meta.AppendArrayItem ( kNS1, "Array1", 0, "two" );
+ meta.AppendArrayItem ( kNS1, "Array1", kXMP_PropValueIsArray, "3, three" );
+ meta.AppendArrayItem ( kNS1, "Array1", 0, "4; four" );
+
+ DumpXMPObj ( log, meta, "Initial array" );
+ fprintf ( log, "\n" );
+
+ tmpStr1.erase();
+ SXMPUtils::CatenateArrayItems ( meta, kNS1, "Array1", "; ", "\"", kXMP_NoOptions, &tmpStr1 );
+ fprintf ( log, "CatenateArrayItems, no commas : %s\n", tmpStr1.c_str() );
+
+ tmpStr2.erase();
+ SXMPUtils::CatenateArrayItems ( meta, kNS1, "Array1", " ; ", "\"", kXMPUtil_AllowCommas, &tmpStr2 );
+ fprintf ( log, "CatenateArrayItems, allow commas : %s\n", tmpStr2.c_str() );
+
+ SXMPUtils::SeparateArrayItems ( &meta, kNS1, "Array2-1", kXMP_NoOptions, tmpStr1.c_str() );
+ SXMPUtils::SeparateArrayItems ( &meta, kNS1, "Array2-2", kXMPUtil_AllowCommas, tmpStr1.c_str() );
+
+ SXMPUtils::SeparateArrayItems ( &meta, kNS1, "Array3-1", kXMP_PropArrayIsOrdered, tmpStr2 );
+ SXMPUtils::SeparateArrayItems ( &meta, kNS1, "Array3-2", (kXMP_PropArrayIsOrdered | kXMPUtil_AllowCommas), tmpStr2 );
+
+ DumpXMPObj ( log, meta, "Set Array1, cat and split into others" );
+
+ SXMPUtils::SeparateArrayItems ( &meta, kNS1, "Array2-2", kXMP_NoOptions, tmpStr1.c_str() ); // Repeat into existing arrays.
+ SXMPUtils::SeparateArrayItems ( &meta, kNS1, "Array3-2", kXMP_PropArrayIsOrdered, tmpStr2.c_str() );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+
+ {
+ WriteMajorLabel ( log, "Test RemoveProperties and AppendProperties" );
+
+ SXMPMeta meta1 ( kSimpleRDF, strlen(kSimpleRDF) );
+
+ meta1.SetProperty ( kNS2, "Prop", "value" );
+ DumpXMPObj ( log, meta1, "Parse simple RDF, add ns2:Prop" );
+
+ SXMPUtils::RemoveProperties ( &meta1, kNS1, "ArrayOfStructProp" );
+ DumpXMPObj ( log, meta1, "Remove ns1:ArrayOfStructProp" );
+
+ SXMPUtils::RemoveProperties ( &meta1, kNS1 );
+ DumpXMPObj ( log, meta1, "Remove all of ns1:" );
+
+ meta1.SetProperty ( kXMP_NS_XMP, "CreatorTool", "XMPCoverage" );
+ meta1.SetProperty ( kXMP_NS_XMP, "Nickname", "TXMP test" );
+ DumpXMPObj ( log, meta1, "Set xmp:CreatorTool (internal) and xmp:Nickname (external)" );
+
+ SXMPUtils::RemoveProperties ( &meta1 );
+ DumpXMPObj ( log, meta1, "Remove all external properties" );
+
+ SXMPUtils::RemoveProperties ( &meta1, 0, 0, kXMPUtil_DoAllProperties );
+ DumpXMPObj ( log, meta1, "Remove all properties, including internal" );
+
+ meta1.SetProperty ( kXMP_NS_XMP, "CreatorTool", "XMPCoverage" );
+ meta1.SetProperty ( kXMP_NS_XMP, "Nickname", "TXMP test" );
+ DumpXMPObj ( log, meta1, "Set xmp:CreatorTool and xmp:Nickname again" );
+
+ SXMPMeta meta2 ( kSimpleRDF, strlen(kSimpleRDF) );
+
+ meta2.SetProperty ( kXMP_NS_XMP, "CreatorTool", "new CreatorTool" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Nickname", "new Nickname" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Format", "new Format" );
+ DumpXMPObj ( log, meta2, "Create 2nd XMP object with new values" );
+
+ SXMPUtils::ApplyTemplate ( &meta1, meta2, kXMPTemplate_AddNewProperties );
+ DumpXMPObj ( log, meta1, "Append 2nd to 1st, keeping old values, external only" );
+
+ meta2.SetProperty ( kXMP_NS_XMP, "CreatorTool", "newer CreatorTool" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Nickname", "newer Nickname" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Format", "newer Format" );
+ SXMPUtils::ApplyTemplate ( &meta1, meta2, kXMPTemplate_AddNewProperties | kXMPTemplate_IncludeInternalProperties );
+ DumpXMPObj ( log, meta1, "Append 2nd to 1st, keeping old values, internal also" );
+
+ meta2.SetProperty ( kXMP_NS_XMP, "CreatorTool", "newest CreatorTool" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Nickname", "newest Nickname" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Format", "newest Format" );
+ SXMPUtils::ApplyTemplate ( &meta1, meta2, kXMPTemplate_AddNewProperties | kXMPTemplate_ReplaceExistingProperties );
+ DumpXMPObj ( log, meta1, "Append 2nd to 1st, replacing old values, external only" );
+
+ meta2.SetProperty ( kXMP_NS_XMP, "CreatorTool", "final CreatorTool" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Nickname", "final Nickname" );
+ meta2.SetProperty ( kXMP_NS_XMP, "Format", "final Format" );
+ SXMPUtils::ApplyTemplate ( &meta1, meta2, kXMPTemplate_AddNewProperties | kXMPTemplate_ReplaceExistingProperties | kXMPTemplate_IncludeInternalProperties );
+ DumpXMPObj ( log, meta1, "Append 2nd to 1st, replacing old values, internal also" );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+
+ {
+ WriteMajorLabel ( log, "Test DuplicateSubtree" );
+
+ SXMPMeta meta1 ( kSimpleRDF, strlen(kSimpleRDF) );
+ SXMPMeta meta2;
+
+ SXMPUtils::DuplicateSubtree ( meta1, &meta2, kNS1, "ArrayOfStructProp" );
+ DumpXMPObj ( log, meta2, "DuplicateSubtree to default destination" );
+
+ #if 1 // The underlying old toolkit does not support changing the schema namespace.
+
+ SXMPUtils::DuplicateSubtree ( meta1, &meta2, kNS1, "ArrayOfStructProp", kNS2, "NewAoS" );
+ DumpXMPObj ( log, meta2, "DuplicateSubtree to different destination" );
+
+ SXMPUtils::DuplicateSubtree ( meta1, &meta1, kNS1, "ArrayOfStructProp", kNS2, "NewAoS" );
+ DumpXMPObj ( log, meta1, "DuplicateSubtree to different destination in same object" );
+
+ #else
+
+ SXMPUtils::DuplicateSubtree ( meta1, &meta2, kNS1, "ArrayOfStructProp", kNS1, "NewAoS" );
+ DumpXMPObj ( log, meta2, "DuplicateSubtree to different destination" );
+
+ SXMPUtils::DuplicateSubtree ( meta1, &meta1, kNS1, "ArrayOfStructProp", kNS1, "NewAoS" );
+ DumpXMPObj ( log, meta1, "DuplicateSubtree to different destination in same object" );
+
+ #endif
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+
+ {
+ WriteMajorLabel ( log, "Test EncodeToBase64 and DecodeFromBase64" );
+ fprintf ( log, "\n" );
+
+ unsigned long m;
+
+ #if UseStringPushBack
+ #define PushBack(s,c) s.push_back ( c )
+ #else
+ #define PushBack(s,c) s.insert ( s.end(), c );
+ #endif
+
+ tmpStr1.erase();
+ for ( i = 0; i < 64; i += 4 ) {
+ m = (i << 18) + ((i+1) << 12) + ((i+2) << 6) + (i+3);
+ PushBack ( tmpStr1, ((char) (m >> 16)) );
+ PushBack ( tmpStr1, ((char) ((m >> 8) & 0xFF)) );
+ PushBack ( tmpStr1, ((char) (m & 0xFF)) );
+ }
+
+ tmpStr2.erase();
+ SXMPUtils::EncodeToBase64 ( tmpStr1, &tmpStr2 );
+ fprintf ( log, "Encoded sequence (should be A-Za-z0-9+/) : %s\n", tmpStr2.c_str() );
+
+ tmpStr3.erase();
+ SXMPUtils::DecodeFromBase64 ( tmpStr2, &tmpStr3 );
+ if ( tmpStr1 != tmpStr3 ) fprintf ( log, "** Error in base 64 round trip\n" );
+
+ }
+
+ // --------------------------------------------------------------------------------------------
+
+ WriteMajorLabel ( log, "XMPCoreCoverage done" );
+ fprintf ( log, "\n" );
+
+} // DoXMPCoreCoverage
+
+// =================================================================================================
+
+extern "C" int main ( int /*argc*/, const char * argv [] )
+{
+ int result = 0;
+
+ char logName[256];
+ int nameLen = strlen ( argv[0] );
+ if ( (nameLen >= 4) && (strcmp ( argv[0]+nameLen-4, ".exe" ) == 0) ) nameLen -= 4;
+ memcpy ( logName, argv[0], nameLen );
+ memcpy ( &logName[nameLen], "Log.txt", 8 ); // Include final null.
+ FILE * log = fopen ( logName, "wb" );
+
+ time_t now = time(0);
+ fprintf ( log, "XMPCoreCoverage starting %s", ctime(&now) );
+
+ XMP_VersionInfo version;
+ SXMPMeta::GetVersionInfo ( &version );
+ fprintf ( log, "Version : %s\n" , version.message );
+
+ try {
+
+// *** Add memory leak checking for both DLL and static builds ***
+
+ if ( ! SXMPMeta::Initialize() ) {
+ fprintf ( log, "## XMPMeta::Initialize failed!\n" );
+ return -1;
+ }
+ DoXMPCoreCoverage ( log );
+
+ } catch ( XMP_Error & excep ) {
+
+ fprintf ( log, "\nCaught XMP_Error %d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ result = -2;
+
+ } catch ( ... ) {
+
+ fprintf ( log, "## Caught unknown exception\n" );
+ result = -3;
+
+ }
+
+ SXMPMeta::Terminate();
+
+ now = time(0);
+ fprintf ( log, "XMPCoreCoverage finished %s", ctime(&now) );
+ fprintf ( log, "Final status = %d\n", result );
+ fclose ( log );
+
+ printf( "results have been logged into %s\n", logName );
+
+ return result;
+
+}
diff --git a/samples/source/XMPFilesCoverage.cpp b/samples/source/XMPFilesCoverage.cpp
new file mode 100644
index 0000000..216369f
--- /dev/null
+++ b/samples/source/XMPFilesCoverage.cpp
@@ -0,0 +1,343 @@
+// =================================================================================================
+// Copyright 2002 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* Demonstrates syntax and usage by exercising most of the API functions of XMPFiles Toolkit SDK component,
+* using a sample XMP Packet that contains all of the different property and attribute types.
+*/
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <stdexcept>
+#include <cerrno>
+#include <ctime>
+
+#define TXMP_STRING_TYPE std::string
+#define XMP_INCLUDE_XMPFILES 1
+#include "public/include/XMP.hpp"
+#include "public/include/XMP.incl_cpp"
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+using namespace std;
+
+#if WIN_ENV
+ #pragma warning ( disable : 4100 ) // ignore unused variable
+ #pragma warning ( disable : 4127 ) // conditional expression is constant
+ #pragma warning ( disable : 4505 ) // unreferenced local function has been removed
+ #pragma warning ( disable : 4996 ) // '...' was declared deprecated
+#endif
+
+// -------------------------------------------------------------------------------------------------
+
+FILE * sLogFile = 0;
+
+// -------------------------------------------------------------------------------------------------
+
+static void WriteMinorLabel ( FILE * log, const char * title )
+{
+
+ fprintf ( log, "\n// " );
+ for ( size_t i = 0; i < strlen(title); ++i ) fprintf ( log, "-" );
+ fprintf ( log, "--\n// %s :\n\n", title );
+
+} // WriteMinorLabel
+
+// -------------------------------------------------------------------------------------------------
+
+static XMP_Status DumpToFile ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ XMP_Status status = 0;
+ size_t count;
+ FILE * outFile = static_cast < FILE * > ( refCon );
+
+ count = fwrite ( outStr, 1, outLen, outFile );
+ if ( count != outLen ) status = errno;
+ return status;
+
+} // DumpToFile
+
+// -------------------------------------------------------------------------------------------------
+
+static XMP_Status DumpToString ( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+{
+ std::string * fullStr = static_cast < std::string * > ( refCon );
+
+ fullStr->append ( outStr, outLen );
+ return 0;
+
+} // DumpToString
+
+// -------------------------------------------------------------------------------------------------
+
+#define DumpOneFormat(fmt) \
+ format = kXMP_ ## fmt ## File; \
+ flags = 0; \
+ ok = SXMPFiles::GetFormatInfo ( format, &flags ); \
+ fprintf ( sLogFile, "kXMP_" #fmt "File = %.8X, %s, flags = 0x%X\n", \
+ format, (ok ? "smart" : "dumb"), flags );
+
+static void DumpHandlerInfo()
+{
+
+ WriteMinorLabel ( sLogFile, "Dump file format constants and handler flags" );
+
+ bool ok;
+ XMP_FileFormat format;
+ XMP_OptionBits flags;
+
+ DumpOneFormat ( PDF );
+ DumpOneFormat ( PostScript );
+ DumpOneFormat ( EPS );
+
+ DumpOneFormat ( JPEG );
+ DumpOneFormat ( JPEG2K );
+ DumpOneFormat ( TIFF );
+ DumpOneFormat ( GIF );
+ DumpOneFormat ( PNG );
+
+ DumpOneFormat ( MOV );
+ DumpOneFormat ( AVI );
+ DumpOneFormat ( CIN );
+ DumpOneFormat ( WAV );
+ DumpOneFormat ( MP3 );
+ DumpOneFormat ( SES );
+ DumpOneFormat ( CEL );
+ DumpOneFormat ( MPEG );
+ DumpOneFormat ( MPEG2 );
+ DumpOneFormat ( WMAV );
+
+ DumpOneFormat ( HTML );
+ DumpOneFormat ( XML );
+ DumpOneFormat ( Text );
+
+ DumpOneFormat ( Photoshop );
+ DumpOneFormat ( Illustrator );
+ DumpOneFormat ( InDesign );
+ DumpOneFormat ( AEProject );
+ DumpOneFormat ( AEFilterPreset );
+ DumpOneFormat ( EncoreProject );
+ DumpOneFormat ( PremiereProject );
+ DumpOneFormat ( PremiereTitle );
+
+ DumpOneFormat ( Unknown );
+
+} // DumpHandlerInfo
+
+// -------------------------------------------------------------------------------------------------
+
+static void OpenTestFile ( const char * fileName, XMP_OptionBits rwMode, SXMPMeta* xmpMeta, SXMPFiles* xmpFile )
+{
+ bool ok;
+
+ XMP_FileFormat format;
+ XMP_OptionBits openFlags, handlerFlags;
+ XMP_PacketInfo xmpPacket;
+
+ bool isUpdate = ((rwMode & kXMPFiles_OpenForUpdate) != 0);
+
+ static const char * charForms[] = { "UTF-8", "unknown char form", "UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE" };
+
+ XMP_OptionBits smartFlags = rwMode | kXMPFiles_OpenUseSmartHandler;
+ XMP_OptionBits scanFlags = rwMode | kXMPFiles_OpenUsePacketScanning;
+
+ ok = xmpFile->OpenFile ( fileName, kXMP_UnknownFile, smartFlags );
+ if ( ! ok ) {
+ fprintf ( sLogFile, "Failed to get a smart handler\n" );
+ ok = xmpFile->OpenFile ( fileName, kXMP_UnknownFile, scanFlags );
+ if ( ! ok ) return;
+ }
+
+ ok = xmpFile->GetFileInfo ( 0, &openFlags, &format, &handlerFlags );
+ if ( ! ok ) return;
+
+ fprintf ( sLogFile, "File info : format = %.8X, handler flags = 0x%X, open flags = 0x%X (%s)\n",
+ format, handlerFlags, openFlags, (isUpdate ? "update" : "read-only") );
+
+ ok = xmpFile->GetXMP ( xmpMeta, 0, &xmpPacket );
+ if ( ! ok ) {
+ fprintf ( sLogFile, "No XMP\n" );
+ return;
+ }
+
+ fprintf ( sLogFile, "XMP packet info : file offset = %lld, length = %d, pad = %d",
+ xmpPacket.offset, xmpPacket.length, xmpPacket.padSize );
+ fprintf ( sLogFile, ", %s", ((xmpPacket.charForm > 5) ? "bad char form" : charForms[xmpPacket.charForm]) );
+ fprintf ( sLogFile, ", %s\n", (xmpPacket.writeable ? "writeable" : "read-only") );
+
+ fprintf ( sLogFile, "\n" );
+
+ // Remove extaneous properties to make the dump smaller.
+ SXMPUtils::RemoveProperties ( xmpMeta, kXMP_NS_XMP, "Thumbnails", true );
+ SXMPUtils::RemoveProperties ( xmpMeta, kXMP_NS_XMP, "PageInfo", true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/xap/1.0/t/pg/", 0, true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/xap/1.0/mm/", 0, true );
+ #if 1
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/camera-raw-settings/1.0/", 0, true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/tiff/1.0/", 0, true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/exif/1.0/", 0, true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/exif/1.0/aux/", 0, true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/photoshop/1.0/", 0, true );
+ SXMPUtils::RemoveProperties ( xmpMeta, "http://ns.adobe.com/pdf/1.3/", 0, true );
+ #endif
+
+} // OpenTestFile
+
+// -------------------------------------------------------------------------------------------------
+
+#ifndef OnlyReadOnly
+ #define OnlyReadOnly 0
+#endif
+
+static void TestOneFile ( const char * fileName )
+{
+ char buffer [1000];
+ SXMPMeta xmpMeta;
+ SXMPFiles xmpFile;
+ XMP_PacketInfo xmpPacket;
+ std::string roDump, rwDump;
+
+ sprintf ( buffer, "Testing %s", fileName );
+ WriteMinorLabel ( sLogFile, buffer );
+
+ OpenTestFile ( fileName, kXMPFiles_OpenForRead, &xmpMeta, &xmpFile );
+ xmpMeta.DumpObject ( DumpToString, &roDump );
+ xmpFile.CloseFile();
+
+ if ( OnlyReadOnly ) {
+ fprintf ( sLogFile, "Initial XMP from %s\n", fileName );
+ xmpMeta.DumpObject ( DumpToFile, sLogFile );
+ return;
+ }
+
+ OpenTestFile ( fileName, kXMPFiles_OpenForUpdate, &xmpMeta, &xmpFile );
+ xmpMeta.DumpObject ( DumpToString, &rwDump );
+ if ( roDump != rwDump ) {
+ fprintf ( sLogFile, "** Initial read-only and update XMP don't match! **\n\n" );
+ fprintf ( sLogFile, "Read-only XMP\n%s\nUpdate XMP\n%s\n", roDump.c_str(), rwDump.c_str() );
+ }
+
+ fprintf ( sLogFile, "Initial XMP from %s\n", fileName );
+ xmpMeta.DumpObject ( DumpToFile, sLogFile );
+
+ XMP_DateTime now;
+ SXMPUtils::CurrentDateTime ( &now );
+ std::string nowStr;
+ SXMPUtils::ConvertFromDate ( now, &nowStr );
+ if ( xmpMeta.CountArrayItems ( kXMP_NS_XMP, "XMPFileStamps" ) >= 9 ) {
+ xmpMeta.DeleteProperty ( kXMP_NS_XMP, "XMPFileStamps" );
+ }
+ xmpMeta.AppendArrayItem ( kXMP_NS_XMP, "XMPFileStamps", kXMP_PropArrayIsOrdered, "" );
+ xmpMeta.SetProperty ( kXMP_NS_XMP, "XMPFileStamps[last()]", nowStr.c_str() );
+
+ nowStr.insert ( 0, "Updating dc:description at " );
+ xmpMeta.SetLocalizedText ( kXMP_NS_DC, "description", "", "x-default", nowStr.c_str() );
+
+ xmpFile.PutXMP ( xmpMeta );
+ XMP_OptionBits closeOptions = 0;
+ XMP_OptionBits capabilities = 0;
+ XMP_FileFormat fileFormat = 0;
+ xmpFile.GetFileInfo( NULL, NULL, &fileFormat, NULL);
+ SXMPFiles::GetFormatInfo( fileFormat, &capabilities);
+
+ if ( (xmpMeta.CountArrayItems ( kXMP_NS_XMP, "XMPFileStamps" ) & 1) == 0
+ && ( capabilities & kXMPFiles_AllowsSafeUpdate ) )
+ closeOptions |= kXMPFiles_UpdateSafely;
+
+ xmpFile.CloseFile ( closeOptions );
+
+ fprintf ( sLogFile, "\n" );
+ OpenTestFile ( fileName, kXMPFiles_OpenForRead, &xmpMeta, &xmpFile );
+ fprintf ( sLogFile, "Modified XMP from %s\n", fileName );
+ xmpMeta.DumpObject ( DumpToFile, sLogFile );
+ xmpFile.CloseFile();
+
+ fprintf ( sLogFile, "\nDone testing %s\n", fileName );
+
+} // TestOneFile
+
+// -------------------------------------------------------------------------------------------------
+
+extern "C" int main ( int argc, const char * argv[] )
+{
+ int result = 0;
+
+ char logName[256];
+ int nameLen = (int) strlen ( argv[0] );
+ if ( (nameLen >= 4) && (strcmp ( argv[0]+nameLen-4, ".exe" ) == 0) ) nameLen -= 4;
+ memcpy ( logName, argv[0], nameLen );
+ memcpy ( &logName[nameLen], "Log.txt", 8 ); // Include final null.
+ sLogFile = fopen ( logName, "wb" );
+
+ time_t now = time(0);
+ fprintf ( sLogFile, "XMPFilesCoverage starting %s", ctime(&now) );
+
+ XMP_VersionInfo coreVersion, filesVersion;
+ SXMPMeta::GetVersionInfo ( &coreVersion );
+ SXMPFiles::GetVersionInfo ( &filesVersion );
+ fprintf ( sLogFile, "Version :\n %s\n %s\n", coreVersion.message, filesVersion.message );
+
+ try {
+
+ if ( ! SXMPMeta::Initialize() ) {
+ fprintf ( sLogFile, "## XMPMeta::Initialize failed!\n" );
+ return -1;
+ }
+ XMP_OptionBits options = 0;
+ #if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+ #endif
+ if ( ! SXMPFiles::Initialize ( options ) ) {
+ fprintf ( sLogFile, "## SXMPFiles::Initialize failed!\n" );
+ return -1;
+ }
+
+ DumpHandlerInfo();
+
+ TestOneFile ( "../../../testfiles/BlueSquare.ai" );
+ TestOneFile ( "../../../testfiles/BlueSquare.eps" );
+ TestOneFile ( "../../../testfiles/BlueSquare.indd" );
+ TestOneFile ( "../../../testfiles/BlueSquare.jpg" );
+ TestOneFile ( "../../../testfiles/BlueSquare.pdf" );
+ TestOneFile ( "../../../testfiles/BlueSquare.psd" );
+ TestOneFile ( "../../../testfiles/BlueSquare.tif" );
+ TestOneFile ( "../../../testfiles/BlueSquare.avi" );
+ TestOneFile ( "../../../testfiles/BlueSquare.mov" );
+ TestOneFile ( "../../../testfiles/BlueSquare.mp3" );
+ TestOneFile ( "../../../testfiles/BlueSquare.wav" );
+ TestOneFile ( "../../../testfiles/BlueSquare.png" );
+
+ } catch ( XMP_Error & excep ) {
+
+ fprintf ( sLogFile, "\nCaught XMP_Error %d : %s\n", excep.GetID(), excep.GetErrMsg() );
+ result = -2;
+
+ } catch ( ... ) {
+
+ fprintf ( sLogFile, "## Caught unknown exception\n" );
+ result = -3;
+
+ }
+
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ now = time(0);
+ fprintf ( sLogFile, "\nXMPFilesCoverage finished %s", ctime(&now) );
+ fprintf ( sLogFile, "Final status = %d\n", result );
+ fclose ( sLogFile );
+
+ printf( "\nresults have been logged into %s\n", logName );
+
+ return result;
+
+} // main
diff --git a/samples/source/XMPIterations.cpp b/samples/source/XMPIterations.cpp
new file mode 100644
index 0000000..2ad2182
--- /dev/null
+++ b/samples/source/XMPIterations.cpp
@@ -0,0 +1,332 @@
+// =================================================================================================
+// Copyright 2008 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+ * Demonstrates how to use the iteration utility in the XMPCore component to walk through property trees.
+ */
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+
+//#define ENABLE_XMP_CPP_INTERFACE 1;
+
+// Must be defined to instantiate template classes
+#define TXMP_STRING_TYPE std::string
+
+// Must be defined to give access to XMPFiles
+#define XMP_INCLUDE_XMPFILES 1
+
+// Ensure XMP templates are instantiated
+#include "public/include/XMP.incl_cpp"
+
+// Provide access to the API
+#include "public/include/XMP.hpp"
+
+#include <iostream>
+
+using namespace std;
+
+// Provide some custom XMP
+static const char * rdf =
+"<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>"
+" <rdf:Description rdf:about='' xmlns:xmpTest='http://ns.adobe.com/xmpTest/'>"
+""
+" <xmpTest:MySimpleProp rdf:parseType='Resource'>"
+" <rdf:value>A Value</rdf:value>"
+" <xmpTest:MyQual>Qual Value</xmpTest:MyQual>"
+" </xmpTest:MySimpleProp>"
+""
+" <xmpTest:MyTopStruct rdf:parseType='Resource'>"
+" <xmpTest:MySecondStruct rdf:parseType='Resource'>"
+" <xmpTest:MyThirdStruct rdf:parseType='Resource'>"
+" <xmpTest:MyThirdStructField>Field Value 3</xmpTest:MyThirdStructField>"
+" </xmpTest:MyThirdStruct>"
+" <xmpTest:MySecondStructField>Field Value 2</xmpTest:MySecondStructField>"
+" </xmpTest:MySecondStruct>"
+" <xmpTest:MyTopStructField>Field Value 1</xmpTest:MyTopStructField>"
+" </xmpTest:MyTopStruct>"
+
+" <xmpTest:MyArrayWithNestedArray>"
+" <rdf:Bag>"
+" <rdf:li>"
+" <rdf:Seq>"
+" <rdf:li>Item 1</rdf:li>"
+" <rdf:li>Item 2</rdf:li>"
+" </rdf:Seq>"
+" </rdf:li>"
+" </rdf:Bag>"
+" </xmpTest:MyArrayWithNestedArray>"
+
+" <xmpTest:MyArrayWithStructures>"
+" <rdf:Seq>"
+" <rdf:li rdf:parseType='Resource'>"
+" <rdf:value>Field Value 1</rdf:value>"
+" <xmpTest:FirstQual>Qual Value 1</xmpTest:FirstQual>"
+" <xmpTest:SecondQual>Qual Value 2</xmpTest:SecondQual>"
+" </rdf:li>"
+" <rdf:li rdf:parseType='Resource'>"
+" <rdf:value>Field Value 2</rdf:value>"
+" <xmpTest:FirstQual>Qual Value 3</xmpTest:FirstQual>"
+" <xmpTest:SecondQual>Qual Value 4</xmpTest:SecondQual>"
+" </rdf:li>"
+" </rdf:Seq>"
+" </xmpTest:MyArrayWithStructures>"
+""
+" <xmpTest:MyStructureWithArray rdf:parseType='Resource'>"
+" <xmpTest:NestedArray>"
+" <rdf:Bag>"
+" <rdf:li>Item 3</rdf:li>"
+" <rdf:li>Item 4</rdf:li>"
+" <rdf:li>Item 5</rdf:li>"
+" <rdf:li>Item 6</rdf:li>"
+" </rdf:Bag>"
+" </xmpTest:NestedArray>"
+" <xmpTest:NestedArray2>"
+" <rdf:Bag>"
+" <rdf:li>Item 66</rdf:li>"
+" <rdf:li>Item 46</rdf:li>"
+" <rdf:li>Item 56</rdf:li>"
+" <rdf:li>Item 66</rdf:li>"
+" </rdf:Bag>"
+" </xmpTest:NestedArray2>"
+" </xmpTest:MyStructureWithArray>"
+""
+" </rdf:Description>"
+"</rdf:RDF>";
+
+// The namespace to be used. This will be automatically registered
+// when the RDF is parsed.
+const XMP_StringPtr kXMP_NS_SDK = "http://ns.adobe.com/xmpTest/";
+
+/**
+ * Reads some metadata from a file and appends some custom XMP to it. Then does several
+ * iterations, using various iterators. Each iteration is displayed in the console window.
+ */
+int main()
+{
+ if(SXMPMeta::Initialize())
+ {
+ XMP_OptionBits options = 0;
+#if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+#endif
+ if ( SXMPFiles::Initialize ( options ) ) {
+ bool ok;
+ SXMPFiles myFile;
+
+ XMP_OptionBits opts = kXMPFiles_OpenForRead | kXMPFiles_OpenUseSmartHandler;
+#if MAC_ENV
+ ok = myFile.OpenFile("../../../../testfiles/Image1.jpg", kXMP_UnknownFile, opts);
+#else
+ ok = myFile.OpenFile("../../../testfiles/Image1.jpg", kXMP_UnknownFile, opts);
+#endif
+ if(ok)
+ {
+ SXMPMeta xmp;
+ myFile.GetXMP(&xmp);
+
+ // Add some custom metadata to the XMP object
+ SXMPMeta custXMP(rdf, (XMP_StringLen) strlen(rdf));
+ SXMPUtils::ApplyTemplate(&xmp, custXMP, kXMPTemplate_AddNewProperties);
+
+ // Store any details from the iter.Next() call
+ string schemaNS, propPath, propVal;
+
+ // Only visit the immediate children that are leaf properties of the Dublin Core schema
+ SXMPIterator dcLeafIter(xmp, kXMP_NS_DC, (kXMP_IterJustChildren | kXMP_IterJustLeafNodes));
+ while(dcLeafIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit one property from the XMP Basic schema
+ SXMPIterator xmpKeywordsIter(xmp, kXMP_NS_XMP, "Keywords", kXMP_IterJustLeafNodes);
+ while(xmpKeywordsIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit the Dublin Core schema, omit any quailifiers and only
+ // show the leaf properties
+ SXMPIterator dcIter(xmp, kXMP_NS_DC, (kXMP_IterOmitQualifiers | kXMP_IterJustLeafNodes));
+ while(dcIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit the Dublin Core schema, omit any quailifiers,
+ // show the leaf properties but only return the leaf name and not the full path
+ SXMPIterator dcIter2(xmp, kXMP_NS_DC, (kXMP_IterOmitQualifiers | kXMP_IterJustLeafNodes | kXMP_IterJustLeafName));
+ while(dcIter2.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Iterate over a single namespace. Show all properties within
+ // the Photoshop schema
+ SXMPIterator exifIter(xmp, kXMP_NS_Photoshop);
+ while(exifIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Just visit the leaf nodes of EXIF properties. That is just
+ // properties that may have values.
+ SXMPIterator exifLeafIter(xmp, kXMP_NS_EXIF, kXMP_IterJustLeafNodes);
+ while(exifLeafIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Iterate over all properties but skip the EXIF schema and skip the custom schema
+ // and continue visiting nodes
+ SXMPIterator skipExifIter (xmp);
+ while(skipExifIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ if(schemaNS == kXMP_NS_EXIF || schemaNS == kXMP_NS_SDK)
+ {
+ skipExifIter.Skip(kXMP_IterSkipSubtree);
+ }
+ else
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Iterate over all properties but skip the EXIF schema
+ // and any remaining siblings of the current node.
+ SXMPIterator stopAfterExifIter ( xmp );
+ while(stopAfterExifIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ if(schemaNS == kXMP_NS_EXIF || schemaNS == kXMP_NS_SDK)
+ {
+ stopAfterExifIter.Skip(kXMP_IterSkipSiblings);
+ }
+ else
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+ }
+
+ cout << "----------------------------------" << endl;
+
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ // Iterate over the custom XMP
+
+ // Visit the immediate children of this node.
+ // No qualifiers are visisted as they are below the property being visisted.
+ SXMPIterator justChildrenIter(xmp, kXMP_NS_SDK, kXMP_IterJustChildren);
+ while(justChildrenIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit the immediate children of this node but only those that may have values.
+ // No qualifiers are visisted as they are below the property being visisted.
+ SXMPIterator justChildrenAndLeafIter(xmp, kXMP_NS_SDK, (kXMP_IterJustChildren | kXMP_IterJustLeafNodes));
+ while(justChildrenAndLeafIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit the leaf nodes of TopStructProperty
+ SXMPIterator myTopStructIter(xmp, kXMP_NS_SDK, "MyTopStruct", kXMP_IterJustLeafNodes);
+ while(myTopStructIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit the leaf nodes of the TopStructProperty but only return the names for
+ // the leaf components and not the full path
+ SXMPIterator xmyTopStructIterShortNames(xmp, kXMP_NS_SDK, "MyTopStruct", (kXMP_IterJustLeafNodes | kXMP_IterJustLeafName));
+ while(xmyTopStructIterShortNames.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit a property and all of the qualifiers
+ SXMPIterator iterArrayProp (xmp, kXMP_NS_SDK, "ArrayWithStructures", kXMP_IterJustLeafNodes );
+ while(iterArrayProp.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Visit a property and omit all of the qualifiers
+ SXMPIterator iterArrayPropNoQual (xmp, kXMP_NS_SDK, "ArrayWithStructures", (kXMP_IterJustLeafNodes | kXMP_IterOmitQualifiers));
+ while(iterArrayPropNoQual.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << propPath << " = " << propVal << endl;
+ }
+
+ cout << "----------------------------------" << endl;
+
+ // Skip a subtree and continue onwards. Once 'Item 4' is found then the we can skip all of the
+ // siblings of the current node. If the the current node were a top level node the iteration
+ // would be complete as all siblings would be skipped. However, when 'Item 4' is found the current
+ // node is not at the top level so there are other nodes further up the tree that still need to be
+ // visited.
+ SXMPIterator skipIter (xmp, kXMP_NS_SDK, (kXMP_IterJustLeafNodes | kXMP_IterOmitQualifiers | kXMP_IterJustLeafName));
+ while(skipIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ if(propVal == "Item 4")
+ {
+ skipIter.Skip(kXMP_IterSkipSiblings);
+ }
+ else
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+ }
+
+ /*
+ // Visit all properties and qualifiers
+ SXMPIterator allPropsIter(xmp);
+ while(allPropsIter.Next(&schemaNS, &propPath, &propVal))
+ {
+ cout << schemaNS << " " << propPath << " = " << propVal << endl;
+ }
+ */
+ }
+ }
+ }
+
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+
+ return 0;
+}
+
diff --git a/samples/source/common/DumpFile.cpp b/samples/source/common/DumpFile.cpp
new file mode 100644
index 0000000..dba55dd
--- /dev/null
+++ b/samples/source/common/DumpFile.cpp
@@ -0,0 +1,6255 @@
+// =================================================================================================
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+//
+// DumpFile is suitable for both dumping an entire file structure to screen _as well as_ access to
+// specific to specific legacy fields (as much required for auto-testing).
+//
+// Currently supports
+// - JPEG
+// - TIFF
+// - PHOTOSHOP
+// - JPEG2K
+// - WMAV (ASF/WMA/WMV)
+// - IFF/RIFF (AVI/WAV/RF64/AIFF/AIFF-C)
+// - PNG
+// - InDesign
+// - MP3
+// - MOV (Quicktime)
+// - UCF (done, including commented zips and zip64 (>4GB))
+// - SWF
+// - FLV
+// - MPEG-4
+//
+// DumpFile does depend on XMPCore and the packetscanner from XMPFiles.
+
+
+#include <stdarg.h>
+
+#include "source/ExpatAdapter.hpp"
+
+#include "samples/source/common/globals.h"
+#include "samples/source/common/DumpFile.h"
+#include "samples/source/common/LargeFileAccess.hpp"
+static const XMP_Uns32 CONST_INFINITE = (XMP_Uns32)(-1);
+
+// converts a (supposed) 8Bit-encoded String to Buginese
+// - suitable for UTF-8 or any other encoding
+// - stopOnNull does just that, exceeding length is declared as |R:1234 at the end of string
+// - len is the max parsing size, defaults to unlimited
+// - having set neither stopOnNull nor max parsing size is a bad idea (checked and yields error)
+std::string convert8Bit(void* str, bool stopOnNull, XMP_Uns32 byteLen)
+{
+ std::string r; //result
+ r.clear(); // ...I have become cautious... :-)
+
+ if (byteLen == 0)
+ return r; //nothing to do
+
+ //provoke access violation:
+ //if invalid length leads to access violation, I want it here and now...
+ if (byteLen != CONST_INFINITE) { //if not "CONST_INFINITE"
+ char tmp = ((char*)str)[byteLen - 1];
+ }
+ if (!stopOnNull && (byteLen == CONST_INFINITE))
+ Log::error("must set either stopOnNULL or specify length of string");
+
+ bool outside = false; // toggle-flag: outside ASCII ?
+ XMP_Uns32 remainder = 0;
+ char buffer[200]; //changed from 20 to 200 (whatever reason there was to have it so small)
+
+ for (XMP_Uns32 i = 0;
+ i<byteLen; //either byteLen==0 or run forever (read: till 'break')
+ i++)
+ {
+ XMP_Uns8 ch = ((char*)str)[i];
+ if ((0x20 <= ch) && (ch <= 0x7E))
+ { //outside-case
+ if (outside)
+ r += ">";
+ r += ch;
+ outside = false;
+ }
+ else {
+ if (!outside)
+ r += "<"; //first inside-case
+ else
+ r += " ";
+ sprintf(buffer, "%.2X", ch);
+ r += buffer;
+ outside = true;
+ }
+
+ if (stopOnNull && (ch == 0)) {
+ if (byteLen != CONST_INFINITE) remainder = byteLen - i - 2;
+ break;
+ }
+ }
+
+ if (outside) r += ">";
+ if (remainder>0) {
+ sprintf(buffer, "|R:%d", remainder);
+ r += buffer;
+ }
+ return r;
+}
+
+
+//same story, but for (UTF-)16BE characters
+//note: length still to be specified in byte, thus must be even (verified)
+std::string convert16Bit(bool bigEndian, XMP_Uns8* str, bool stopOnNull, XMP_Uns32 byteLen)
+{
+ //if invalid length leads to access violation, I want it here and now...
+ if (byteLen != CONST_INFINITE) {
+ char tmp = str[byteLen - 1];
+ }
+
+ if (!stopOnNull && (byteLen == CONST_INFINITE))
+ Log::error("must set either stopOnNULL or specify length of string");
+ if ((byteLen != CONST_INFINITE) && (byteLen % 2 != 0)) //if neither CONST_INFINITE or even...#
+ Log::error("convert16BitToBuginese: byteLen must be even");
+
+
+ bool outside = false; // toggle-flag: outside ASCII ?
+ XMP_Uns32 remainder = 0;
+ char buffer[20];
+
+ std::string r; //result
+ r.clear(); // ...I have become cautious... :-)
+
+ for (XMP_Uns32 i = 0;
+ i<byteLen; //either byteLen==0 or run forever (read: till 'break')
+ i = i + 2)
+ {
+ XMP_Uns16 ch = (bigEndian) ? GetUns16BE(&str[i]) : GetUns16LE(&str[i]);
+
+ if ((0x20 <= ch) && (ch <= 0x7E))
+ { //outside-case
+ if (outside)
+ r += ">";
+ r += (char)ch;
+ outside = false;
+ }
+ else {
+ if (!outside)
+ r += "<"; //first inside-case
+ else
+ r += " ";
+ //I want to reflect actual byte order when dumping, thus byte-wise here and no endian-fork
+ sprintf(buffer, "%.2X %.2X", str[i], str[i + 1]);
+ r += buffer;
+ outside = true;
+ }
+
+ if (stopOnNull && (ch == 0)) {
+ if (byteLen != CONST_INFINITE) remainder = byteLen - i - 2;
+ break;
+ }
+ }
+
+ if (outside) r += ">";
+ if (remainder>0) {
+ sprintf(buffer, "|R:%d", remainder);
+ r += buffer;
+ }
+ return r;
+}
+
+
+std::string fromArgs(const char* format, ...)
+{
+ //note: format and ... are somehow "used up", i.e. dumping them
+ // via vsprintf _and_ via printf brought up errors on Mac (only)
+ // i.e. %d %X stuff looking odd (roughly like signed vs unsigned...)
+ // buffer reuse is fine, just dont use format/... twice.
+
+ char buffer[4096]; //should be big enough but no guarantees..
+ va_list args;
+ va_start(args, format);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ return std::string(buffer);
+}
+
+#include <iostream>
+#include <stdarg.h> //fpr va_list et al
+
+DumpFileException::DumpFileException(const char *format, ...) : std::runtime_error("dumpfile exception")
+{
+ va_list args; va_start(args, format);
+ vsnprintf(buffer, XMPQE_MAX_ERROR_LENGTH, format, args);
+ va_end(args);
+}
+
+// overriding, since the buffer needs to be constructed first...
+const char* DumpFileException::what_dumpfile_reason()
+{
+ return buffer;
+}
+
+// REMOVED ON PURPOSE: #include <assert.h>
+// define two assert macros, /w and w/o msg
+// (we don't want to slip malformed files through. Neither in release mode.)
+#undef assertMsg
+#undef assert
+
+//TODO: integrate file pos in parse failure description (LFA_Tell ()...)
+
+// this method just
+#define assertNoThrowMsg(msg,c) \
+try { c } \
+catch ( ... ) { \
+ throw DumpFileException( "- assert: %s\n- message: %s\n- location: " __FILE__ ":%u", \
+ #c, std::string( msg ).c_str(), __LINE__ ); \
+}
+
+#define assertMsg(msg,c) \
+if ( ! (c) ) { \
+ throw DumpFileException( "- assert: %s\n- message: %s\n- location: " __FILE__ ":%u", #c, std::string( msg ).c_str(), __LINE__ ); \
+}
+
+#define assert(c) \
+if ( ! (c) ) { \
+ throw DumpFileException( "- assert: %s\n- location: " __FILE__ ":%u", #c, __LINE__ ); \
+}
+
+#define assertEOF(file) \
+if ( ! LFA_isEof(file) ) { \
+ throw DumpFileException( "- assert: feof(file)\n- message: end of file not reached, still at 0x%X\n- location: " __FILE__ ":%u", LFA_Tell (file), __LINE__ ); \
+}
+
+#define fail(msg) \
+ throw DumpFileException( "- failure\n- message: %s\n- location: " __FILE__ ":%u", std::string( msg ).c_str(), __LINE__ );
+
+using namespace std;
+
+//XMPCore related
+//! no use of XMPFiles
+//! no "XMP.incl_cpp" here, happens in Dumpfile/main.cpp resp. CppUnit/main.cpp
+#define TXMP_STRING_TYPE std::string
+#include "public/include/XMP.hpp"
+#include "public/include/XMP_Const.h"
+
+#include "samples/source/common/XMPScanner.hpp"
+#include "samples/source/common/Log.h"
+//disabled warning (take-over)
+#if XMP_WinBuild
+#pragma warning (disable : 4996) // '...' was declared deprecated
+#pragma warning (disable : 4244) // conversion from '__w64 int' to 'XMP_Uns32', possible loss of data
+#pragma warning (disable : 4267) // conversion from 'size_t' to 'int', possible loss of data
+#endif
+
+#pragma pack (1)
+
+//the tag tree to be build up,
+// then dumped (dumpfile.exe) resp.
+// resp. queried (testrunner)
+static TagTree* tree;
+
+// specifc 'state machine' for QT/MPEG4 dumping
+// * false by default (set in DumpISO() stub)
+static bool TimeCodeTrack;
+
+// =================================================================================================
+
+long kOne = 1;
+char firstByte = *((char*)&kOne);
+
+const bool sBigEndianHost = (firstByte == 0);
+const bool sLittleEndianHost = (firstByte == 1);
+static bool beTIFF;
+
+typedef const char * ChPtr;
+#define CheckBytes(left,right,len) (memcmp (((ChPtr)(left)), ((ChPtr)(right)), len) == 0)
+
+static XMP_Uns8* sDataPtr = 0; // Used via CaptureFileData for variable length data.
+static XMP_Uns32 sDataMax = 0;
+static XMP_Uns32 sDataLen = 0;
+
+// storing XMP Info 'globally' for a later dump...
+static XMP_Uns8* sXMPPtr = 0; // Used via CaptureXMP for the main XMP.
+static XMP_Uns32 sXMPMax = 0;
+static XMP_Uns32 sXMPLen = 0;
+static XMP_Int64 sXMPPos = 0;
+
+typedef XMP_Uns16(*GetUns16_Proc) (const void * addr);
+typedef XMP_Uns32(*GetUns32_Proc) (const void * addr);
+typedef XMP_Uns64(*GetUns64_Proc) (const void * addr);
+
+static XMP_Uns16 GetUns16BE(const void * addr);
+static XMP_Uns16 GetUns16LE(const void * addr);
+static XMP_Uns32 GetUns32BE(const void * addr);
+static XMP_Uns32 GetUns32LE(const void * addr);
+static XMP_Uns64 GetUns64BE(const void * addr);
+static XMP_Uns64 GetUns64LE(const void * addr);
+
+#define High32(u64) ((XMP_Uns32)((u64) >> 32))
+#define Low32(u64) ((XMP_Uns32)((u64) & 0xFFFFFFFFUL))
+
+// =================================================================================================
+
+// ahead declarations
+struct JpegMarker {
+ XMP_Uns8 * jpegMarkerPtr;
+ XMP_Uns16 jpegMarkerLen;
+};
+typedef std::vector<JpegMarker> JpegMarkers;
+
+static void DumpTIFF(XMP_Uns8 * tiffContent, XMP_Uns32 tiffLen, XMP_Uns32 fileOffset, const char * label, std::string path, bool isHeaderAbsent = false);
+static void DumpTIFF(const JpegMarkers& psirMarkers, XMP_Uns8 * dataStart, const char * label, std::string path);
+static void DumpIPTC(XMP_Uns8 * iptcOrigin, XMP_Uns32 iptcLen, XMP_Uns32 fileOffset, const char * label);
+static void DumpImageResources(XMP_Uns8 * psirOrigin, XMP_Uns32 psirLen, XMP_Uns32 fileOffset, const char * label);
+static void DumpImageResources(const JpegMarkers& psirMarkers, XMP_Uns8 * dataStart, const char * label);
+static void DumpIFDChain(XMP_Uns8 * startPtr, XMP_Uns8 * endPtr, XMP_Uns8 * tiffContent, XMP_Uns32 fileOffset, const char * label, std::string path, bool isHeaderAbsent = false);
+
+// =================================================================================================
+
+static GetUns16_Proc TIFF_GetUns16 = 0; // Keeps endian procs for the "current" TIFF.
+static GetUns32_Proc TIFF_GetUns32 = 0;
+static GetUns64_Proc TIFF_GetUns64 = 0;
+
+enum { // Special TIFF tags
+ kTIFF_XMP = 700,
+ kTIFF_IPTC = 33723,
+ kTIFF_PSIR = 34377,
+ kTIFF_Exif = 34665,
+ kTIFF_GPS = 34853,
+ kTIFF_MakerNote = 37500,
+ kTIFF_Interop = 40965
+};
+
+enum { // Special Photoshop image resource IDs
+ kPSIR_OldCaption = 1008,
+ kPSIR_PrintCaption = 1020,
+ kPSIR_IPTC = 1028,
+ kPSIR_CopyrightFlag = 1034,
+ kPSIR_CopyrightURL = 1035,
+ kPSIR_Exif_1 = 1058,
+ kPSIR_Exif_3 = 1059,
+ kPSIR_XMP = 1060,
+ kPSIR_IPTC_Digest = 1061
+};
+
+struct IPTC_DataSet { // The 5 byte header of an IPTC DataSet.
+ XMP_Uns8 tagMarker;
+ XMP_Uns8 recordNumber;
+ XMP_Uns8 dataSetNumber;
+ XMP_Uns8 octetCountHigh;
+ XMP_Uns8 octetCountLow;
+};
+
+enum { // IPTC DataSet IDs
+ kIPTC_IntellectualGenre = 4,
+ kIPTC_Title = 5,
+ kIPTC_Urgency = 10,
+ kIPTC_SubjectCode = 12,
+ kIPTC_Category = 15,
+ kIPTC_SuppCategory = 20,
+ kIPTC_Keyword = 25,
+ kIPTC_Instructions = 40,
+ kIPTC_DateCreated = 55,
+ kIPTC_TimeCreated = 60,
+ kIPTC_DigitalCreationDate = 62,
+ kIPTC_DigitalCreationTime = 63,
+ kIPTC_Creator = 80,
+ kIPTC_CreatorJobtitle = 85,
+ kIPTC_City = 90,
+ kIPTC_Location = 92,
+ kIPTC_State = 95,
+ kIPTC_CountryCode = 100,
+ kIPTC_Country = 101,
+ kIPTC_JobID = 103,
+ kIPTC_Headline = 105,
+ kIPTC_Provider = 110,
+ kIPTC_Source = 115,
+ kIPTC_CopyrightNotice = 116,
+ kIPTC_Description = 120,
+ kIPTC_DescriptionWriter = 122
+};
+
+struct DataSetInfo {
+ XMP_Uns8 id;
+ const char * name;
+};
+
+static const DataSetInfo kDataSetNames[] =
+{ { kIPTC_IntellectualGenre, "Intellectual Genre" },
+{ kIPTC_Title, "Title" },
+{ kIPTC_Urgency, "Urgency" },
+{ kIPTC_SubjectCode, "Subject Code" },
+{ kIPTC_Category, "Category" },
+{ kIPTC_SuppCategory, "Supplemental Category" },
+{ kIPTC_Keyword, "Keyword" },
+{ kIPTC_Instructions, "Instructions" },
+{ kIPTC_DateCreated, "Date Created" },
+{ kIPTC_TimeCreated, "Time Created" },
+{ kIPTC_DigitalCreationDate, "Digital Creation Date" },
+{ kIPTC_DigitalCreationTime, "Digital Creation Time" },
+{ kIPTC_Creator, "Creator" },
+{ kIPTC_CreatorJobtitle, "Creator Jobtitle" },
+{ kIPTC_City, "City" },
+{ kIPTC_Location, "Location" },
+{ kIPTC_State, "Province-State" },
+{ kIPTC_CountryCode, "Country Code" },
+{ kIPTC_Country, "Country" },
+{ kIPTC_JobID, "Job ID" },
+{ kIPTC_Headline, "Headline" },
+{ kIPTC_Provider, "Provider" },
+{ kIPTC_Source, "Source" },
+{ kIPTC_CopyrightNotice, "Copyright Notice" },
+{ kIPTC_Description, "Description" },
+{ kIPTC_DescriptionWriter, "Description Writer" },
+{ 0, 0 } };
+
+enum {
+ kTIFF_Uns8 = 1,
+ kTIFF_ASCII = 2,
+ kTIFF_Uns16 = 3,
+ kTIFF_Uns32 = 4,
+ kTIFF_URational = 5,
+ kTIFF_Int8 = 6,
+ kTIFF_Undef8 = 7,
+ kTIFF_Int16 = 8,
+ kTIFF_Int32 = 9,
+ kTIFF_SRational = 10,
+ kTIFF_Float = 11,
+ kTIFF_Double = 12,
+ kTIFF_IFD = 13,
+ kTIFF_TypeEnd = kTIFF_IFD
+};
+
+static const int sTIFF_TypeSizes[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4 };
+static const char * sTIFF_TypeNames[] = { "", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
+"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL",
+"FLOAT", "DOUBLE" };
+
+struct TagNameInfo {
+ long tag;
+ const char * name;
+};
+
+static const TagNameInfo sTIFF_TagNames[] =
+{ { 256, "ImageWidth" },
+{ 257, "ImageLength" },
+{ 258, "BitsPerSample" },
+{ 259, "Compression" },
+{ 262, "PhotometricInterpretation" },
+{ 270, "ImageDescription" },
+{ 271, "Make" },
+{ 272, "Model" },
+{ 274, "Orientation" },
+{ 282, "XResolution" },
+{ 283, "YResolution" },
+{ 284, "PlanarConfiguration" },
+{ 296, "ResolutionUnit" },
+{ 301, "TransferFunction" },
+{ 305, "Software" },
+{ 306, "DateTime" },
+{ 315, "Artist" },
+{ 318, "WhitePoint" },
+{ 319, "PrimaryChromaticities" },
+{ 529, "YCbCrCoefficients" },
+{ 530, "YCbCrSubSampling" },
+{ 531, "YCbCrPositioning" },
+{ 532, "ReferenceBlackWhite" },
+{ 33432, "Copyright" },
+{ 33434, "ExposureTime" },
+{ 33437, "FNumber" },
+{ 34850, "ExposureProgram" },
+{ 34852, "SpectralSensitivity" },
+{ 34855, "ISOSpeedRatings" },
+{ 34856, "OECF" },
+{ 36864, "ExifVersion" },
+{ 36867, "DateTimeOriginal" },
+{ 36868, "DateTimeDigitized" },
+{ 37121, "ComponentsConfiguration" },
+{ 37122, "CompressedBitsPerPixel" },
+{ 37377, "ShutterSpeedValue" },
+{ 37378, "ApertureValue" },
+{ 37379, "BrightnessValue" },
+{ 37380, "ExposureBiasValue" },
+{ 37381, "MaxApertureValue" },
+{ 37382, "SubjectDistance" },
+{ 37383, "MeteringMode" },
+{ 37384, "LightSource" },
+{ 37385, "Flash" },
+{ 37386, "FocalLength" },
+{ 37396, "SubjectArea" },
+{ 37500, "MakerNote" },
+{ 37510, "UserComment" },
+{ 37520, "SubSecTime" },
+{ 37521, "SubSecTimeOriginal" },
+{ 37522, "SubSecTimeDigitized" },
+{ 40960, "FlashpixVersion" },
+{ 40961, "ColorSpace" },
+{ 40962, "PixelXDimension" },
+{ 40963, "PixelYDimension" },
+{ 40964, "RelatedSoundFile" },
+{ 41483, "FlashEnergy" },
+{ 41484, "SpatialFrequencyResponse" },
+{ 41486, "FocalPlaneXResolution" },
+{ 41487, "FocalPlaneYResolution" },
+{ 41488, "FocalPlaneResolutionUnit" },
+{ 41492, "SubjectLocation" },
+{ 41493, "ExposureIndex" },
+{ 41495, "SensingMethod" },
+{ 41728, "FileSource" },
+{ 41729, "SceneType" },
+{ 41730, "CFAPattern" },
+{ 41985, "CustomRendered" },
+{ 41986, "ExposureMode" },
+{ 41987, "WhiteBalance" },
+{ 41988, "DigitalZoomRatio" },
+{ 41989, "FocalLengthIn35mmFilm" },
+{ 41990, "SceneCaptureType" },
+{ 41991, "GainControl" },
+{ 41992, "Contrast" },
+{ 41993, "Saturation" },
+{ 41994, "Sharpness" },
+{ 41995, "DeviceSettingDescription" },
+{ 41996, "SubjectDistanceRange" },
+{ 42016, "ImageUniqueID" },
+{ 50706, "DNGVersion" },
+{ 50707, "DNGBackwardVersion" },
+{ 50708, "DNG UniqueCameraModel" },
+{ 0, "" } };
+
+// =================================================================================================
+
+struct ASF_GUID {
+
+ XMP_Uns32 part1; // Written little endian.
+ XMP_Uns16 part2; // Written little endian.
+ XMP_Uns16 part3; // Written little endian.
+ XMP_Uns16 part4; // Written big endian.
+ XMP_Uns8 part5[6]; // Written in order.
+
+ ASF_GUID() {};
+ ASF_GUID(XMP_Uns32 p1, XMP_Uns16 p2, XMP_Uns16 p3, XMP_Uns16 p4, const void* p5)
+ {
+ part1 = GetUns32LE(&p1);
+ part2 = GetUns16LE(&p2);
+ part3 = GetUns16LE(&p3);
+ part4 = GetUns16BE(&p4);
+ memcpy(&part5, p5, 6);
+ };
+
+};
+
+enum { // Objects for which we have special knowledge.
+ kASFObj_Unknown = 0,
+ kASFObj_Header, // Special top level objects.
+ kASFObj_Data,
+ kASFObj_XMP,
+ kASFObj_FileProperties, // Children of the Header Object.
+ kASFObj_ContentDesc,
+ kASFObj_ContentBrand,
+ kASFObj_ContentEncrypt,
+ kASFObj_HeaderExtension,
+ kASFObj_Padding
+
+};
+
+static const ASF_GUID kASF_HeaderGUID(0x75B22630, 0x668E, 0x11CF, 0xA6D9, "\x00\xAA\x00\x62\xCE\x6C");
+
+struct ASF_ObjectInfo {
+ ASF_GUID guid;
+ const char * name;
+ XMP_Uns8 kind;
+};
+
+static const ASF_ObjectInfo kASF_KnownObjects[] =
+{
+ { ASF_GUID(0x75B22630, 0x668E, 0x11CF, 0xA6D9, "\x00\xAA\x00\x62\xCE\x6C"), "Header", kASFObj_Header },
+{ ASF_GUID(0x75B22636, 0x668E, 0x11CF, 0xA6D9, "\x00\xAA\x00\x62\xCE\x6C"), "Data", kASFObj_Data },
+{ ASF_GUID(0xBE7ACFCB, 0x97A9, 0x42E8, 0x9C71, "\x99\x94\x91\xE3\xAF\xAC"), "XMP", kASFObj_XMP },
+
+{ ASF_GUID(0x33000890, 0xE5B1, 0x11CF, 0x89F4, "\x00\xA0\xC9\x03\x49\xCB"), "Simple_Index", 0 },
+{ ASF_GUID(0xD6E229D3, 0x35DA, 0x11D1, 0x9034, "\x00\xA0\xC9\x03\x49\xBE"), "Index", 0 },
+{ ASF_GUID(0xFEB103F8, 0x12AD, 0x4C64, 0x840F, "\x2A\x1D\x2F\x7A\xD4\x8C"), "Media_Object_Index", 0 },
+{ ASF_GUID(0x3CB73FD0, 0x0C4A, 0x4803, 0x953D, "\xED\xF7\xB6\x22\x8F\x0C"), "Timecode_Index", 0 },
+
+{ ASF_GUID(0x8CABDCA1, 0xA947, 0x11CF, 0x8EE4, "\x00\xC0\x0C\x20\x53\x65"), "File_Properties", kASFObj_FileProperties },
+{ ASF_GUID(0xB7DC0791, 0xA9B7, 0x11CF, 0x8EE6, "\x00\xC0\x0C\x20\x53\x65"), "Stream_Properties", 0 },
+{ ASF_GUID(0x5FBF03B5, 0xA92E, 0x11CF, 0x8EE3, "\x00\xC0\x0C\x20\x53\x65"), "Header_Extension", kASFObj_HeaderExtension },
+{ ASF_GUID(0x86D15240, 0x311D, 0x11D0, 0xA3A4, "\x00\xA0\xC9\x03\x48\xF6"), "Codec_List", 0 },
+{ ASF_GUID(0x1EFB1A30, 0x0B62, 0x11D0, 0xA39B, "\x00\xA0\xC9\x03\x48\xF6"), "Script_Command", 0 },
+{ ASF_GUID(0xF487CD01, 0xA951, 0x11CF, 0x8EE6, "\x00\xC0\x0C\x20\x53\x65"), "Marker", 0 },
+{ ASF_GUID(0xD6E229DC, 0x35DA, 0x11D1, 0x9034, "\x00\xA0\xC9\x03\x49\xBE"), "Bitrate_Mutual_Exclusion", 0 },
+{ ASF_GUID(0x75B22635, 0x668E, 0x11CF, 0xA6D9, "\x00\xAA\x00\x62\xCE\x6C"), "Error_Correction", 0 },
+{ ASF_GUID(0x75B22633, 0x668E, 0x11CF, 0xA6D9, "\x00\xAA\x00\x62\xCE\x6C"), "Content_Description", kASFObj_ContentDesc },
+{ ASF_GUID(0xD2D0A440, 0xE307, 0x11D2, 0x97F0, "\x00\xA0\xC9\x5E\xA8\x50"), "Extended_Content_Description", 0 },
+{ ASF_GUID(0x2211B3FA, 0xBD23, 0x11D2, 0xB4B7, "\x00\xA0\xC9\x55\xFC\x6E"), "Content_Branding", kASFObj_ContentBrand },
+{ ASF_GUID(0x7BF875CE, 0x468D, 0x11D1, 0x8D82, "\x00\x60\x97\xC9\xA2\xB2"), "Stream_Bitrate_Properties", 0 },
+{ ASF_GUID(0x2211B3FB, 0xBD23, 0x11D2, 0xB4B7, "\x00\xA0\xC9\x55\xFC\x6E"), "Content_Encryption", kASFObj_ContentEncrypt },
+{ ASF_GUID(0x298AE614, 0x2622, 0x4C17, 0xB935, "\xDA\xE0\x7E\xE9\x28\x9C"), "Extended_Content_Encryption", 0 },
+{ ASF_GUID(0x2211B3FC, 0xBD23, 0x11D2, 0xB4B7, "\x00\xA0\xC9\x55\xFC\x6E"), "Digital_Signature", 0 },
+{ ASF_GUID(0x1806D474, 0xCADF, 0x4509, 0xA4BA, "\x9A\xAB\xCB\x96\xAA\xE8"), "Padding", kASFObj_Padding },
+
+{ ASF_GUID(0x14E6A5CB, 0xC672, 0x4332, 0x8399, "\xA9\x69\x52\x06\x5B\x5A"), "Extended_Stream_Properties", 0 },
+{ ASF_GUID(0xA08649CF, 0x4775, 0x4670, 0x8A16, "\x6E\x35\x35\x75\x66\xCD"), "Advanced_Mutual_Exclusion", 0 },
+{ ASF_GUID(0xD1465A40, 0x5A79, 0x4338, 0xB71B, "\xE3\x6B\x8F\xD6\xC2\x49"), "Group_Mutual_Exclusion", 0 },
+{ ASF_GUID(0xD4FED15B, 0x88D3, 0x454F, 0x81F0, "\xED\x5C\x45\x99\x9E\x24"), "Stream_Prioritization", 0 },
+{ ASF_GUID(0xA69609E6, 0x517B, 0x11D2, 0xB6AF, "\x00\xC0\x4F\xD9\x08\xE9"), "Bandwidth_Sharing", 0 },
+{ ASF_GUID(0x7C4346A9, 0xEFE0, 0x4BFC, 0xB229, "\x39\x3E\xDE\x41\x5C\x85"), "Language_List", 0 },
+{ ASF_GUID(0xC5F8CBEA, 0x5BAF, 0x4877, 0x8467, "\xAA\x8C\x44\xFA\x4C\xCA"), "Metadata", 0 },
+{ ASF_GUID(0x44231C94, 0x9498, 0x49D1, 0xA141, "\x1D\x13\x4E\x45\x70\x54"), "Metadata_Library", 0 },
+{ ASF_GUID(0xD6E229DF, 0x35DA, 0x11D1, 0x9034, "\x00\xA0\xC9\x03\x49\xBE"), "Index_Parameters", 0 },
+{ ASF_GUID(0x6B203BAD, 0x3F11, 0x48E4, 0xACA8, "\xD7\x61\x3D\xE2\xCF\xA7"), "Media_Object_Index_Parameters", 0 },
+{ ASF_GUID(0xF55E496D, 0x9797, 0x4B5D, 0x8C8B, "\x60\x4D\xFE\x9B\xFB\x24"), "Timecode_Index_Parameters", 0 },
+{ ASF_GUID(0x75B22630, 0x668E, 0x11CF, 0xA6D9, "\x00\xAA\x00\x62\xCE\x6C"), "Compatibility", 0 },
+{ ASF_GUID(0x43058533, 0x6981, 0x49E6, 0x9B74, "\xAD\x12\xCB\x86\xD5\x8C"), "Advanced_Content_Encryption", 0 },
+
+{ ASF_GUID(0x00000000, 0x0000, 0x0000, 0x0000, "\x00\x00\x00\x00\x00\x00"), 0, 0 }
+};
+
+struct ASF_ObjHeader {
+ ASF_GUID guid;
+ XMP_Int64 size;
+};
+
+struct ASF_FileProperties {
+ ASF_GUID guid;
+ XMP_Int64 size;
+ ASF_GUID fileID;
+ XMP_Int64 fileSize;
+ XMP_Int64 creationDate; // Number of 100-nanosecond intervals since January 1, 1601.
+ XMP_Int64 dataPacketsCount;
+ XMP_Int64 playDuration;
+ XMP_Int64 sendDuration;
+ XMP_Int64 preroll;
+ XMP_Uns32 flags; // The Broadcast flag is bit 0 (lsb).
+ XMP_Uns32 minDataPacketSize;
+ XMP_Uns32 maxDataPacketSize;
+ XMP_Uns32 maxBitrate;
+};
+#define kASF_FilePropertiesSize (16 + 8 + 16 + 6*8 + 4*4)
+
+struct ASF_ContentDescription {
+ ASF_GUID guid;
+ XMP_Int64 size;
+ XMP_Uns16 titleLen;
+ XMP_Uns16 authorLen;
+ XMP_Uns16 copyrightLen;
+ XMP_Uns16 descriptionLen;
+ XMP_Uns16 ratingLen;
+ // Little endian UTF-16 strings follow, no BOM, possible nul terminator.
+};
+#define kASF_ContentDescriptionSize (16 + 8 + 5*2)
+
+#if 0 // ! Has embedded variable length fields!
+struct ASF_ContentBranding {
+ ASF_GUID guid;
+ XMP_Int64 size;
+ XMP_Uns32 bannerType;
+ XMP_Uns32 bannerDataSize;
+ // The banner data is here.
+ XMP_Uns32 bannerURLSize;
+ // The banner URL string is here, an ASCII string.
+ XMP_Uns32 copyrightURLSize;
+ // The copyright URL string is here, an ASCII string.
+};
+#endif
+
+#if 0 // ! Has embedded variable length fields!
+struct ASF_ContentEncryption {
+ ASF_GUID guid;
+ XMP_Int64 size;
+ XMP_Uns32 secretDataSize;
+ // The secret data is here.
+ XMP_Uns32 protectionTypeSize;
+ // The protection type is here, an ASCII string.
+ XMP_Uns32 keyIDSize;
+ // The key ID is here, an ASCII string.
+ XMP_Uns32 licenseURLSize;
+ // The licensed URL is here, an ASCII string.
+};
+#endif
+
+struct ASF_HeaderExtension {
+ ASF_GUID guid;
+ XMP_Int64 size;
+ ASF_GUID reserved1;
+ XMP_Uns16 reserved2;
+ XMP_Uns32 dataLen;
+ // The header extension data is a sequence of nested objects.
+};
+#define kASF_HeaderExtensionSize (16 + 8 + 16 + 2 + 4)
+
+// =================================================================================================
+
+enum {
+ kINDD_PageSize = 4096,
+ kINDD_LittleEndian = 1,
+ kINDD_BigEndian = 2,
+ kInDesignGUIDSize = 16
+};
+
+struct InDesignMasterPage {
+ XMP_Uns8 fGUID[kInDesignGUIDSize];
+ XMP_Uns8 fMagicBytes[8];
+ XMP_Uns8 fObjectStreamEndian;
+ XMP_Uns8 fIrrelevant1[239];
+ XMP_Uns64 fSequenceNumber;
+ XMP_Uns8 fIrrelevant2[8];
+ XMP_Uns32 fFilePages;
+ XMP_Uns8 fIrrelevant3[3812];
+};
+
+static const XMP_Uns8 kInDesign_MasterPageGUID[kInDesignGUIDSize] =
+{ 0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D };
+
+struct InDesignContigObjMarker {
+ XMP_Uns8 fGUID[kInDesignGUIDSize];
+ XMP_Uns32 fObjectUID;
+ XMP_Uns32 fObjectClassID;
+ XMP_Uns32 fStreamLength;
+ XMP_Uns32 fChecksum;
+};
+
+static const XMP_Uns8 kINDDContigObjHeaderGUID[kInDesignGUIDSize] =
+{ 0xDE, 0x39, 0x39, 0x79, 0x51, 0x88, 0x4B, 0x6C, 0x8E, 0x63, 0xEE, 0xF8, 0xAE, 0xE0, 0xDD, 0x38 };
+
+// =================================================================================================
+
+struct FileExtMapping {
+ XMP_StringPtr ext;
+ XMP_FileFormat format;
+};
+
+const FileExtMapping kFileExtMap[] = // Add all known mappings, multiple mappings (tif, tiff) are OK.
+{ { "pdf", kXMP_PDFFile },
+{ "ps", kXMP_PostScriptFile },
+{ "eps", kXMP_EPSFile },
+
+{ "jpg", kXMP_JPEGFile },
+{ "jpeg", kXMP_JPEGFile },
+{ "jpx", kXMP_JPEG2KFile },
+{ "tif", kXMP_TIFFFile },
+{ "tiff", kXMP_TIFFFile },
+{ "gif", kXMP_GIFFile },
+{ "giff", kXMP_GIFFile },
+{ "png", kXMP_PNGFile },
+
+{ "swf", kXMP_SWFFile },
+{ "flv", kXMP_FLVFile },
+
+{ "aif", kXMP_AIFFFile },
+
+{ "mov", kXMP_MOVFile },
+{ "avi", kXMP_AVIFile },
+{ "cin", kXMP_CINFile },
+{ "wav", kXMP_WAVFile },
+{ "mp3", kXMP_MP3File },
+{ "mp4", kXMP_MPEG4File },
+{ "ses", kXMP_SESFile },
+{ "cel", kXMP_CELFile },
+{ "wma", kXMP_WMAVFile },
+{ "wmv", kXMP_WMAVFile },
+
+{ "mpg", kXMP_MPEGFile },
+{ "mpeg", kXMP_MPEGFile },
+{ "mp2", kXMP_MPEGFile },
+{ "mod", kXMP_MPEGFile },
+{ "m2v", kXMP_MPEGFile },
+{ "mpa", kXMP_MPEGFile },
+{ "mpv", kXMP_MPEGFile },
+{ "m2p", kXMP_MPEGFile },
+{ "m2a", kXMP_MPEGFile },
+{ "m2t", kXMP_MPEGFile },
+{ "mpe", kXMP_MPEGFile },
+{ "vob", kXMP_MPEGFile },
+{ "ms-pvr", kXMP_MPEGFile },
+{ "dvr-ms", kXMP_MPEGFile },
+
+{ "html", kXMP_HTMLFile },
+{ "xml", kXMP_XMLFile },
+{ "txt", kXMP_TextFile },
+{ "text", kXMP_TextFile },
+
+{ "psd", kXMP_PhotoshopFile },
+{ "ai", kXMP_IllustratorFile },
+{ "indd", kXMP_InDesignFile },
+{ "indt", kXMP_InDesignFile },
+{ "aep", kXMP_AEProjectFile },
+{ "aet", kXMP_AEProjTemplateFile },
+{ "ffx", kXMP_AEFilterPresetFile },
+{ "ncor", kXMP_EncoreProjectFile },
+{ "prproj", kXMP_PremiereProjectFile },
+{ "prtl", kXMP_PremiereTitleFile },
+
+{ 0, kXMP_UnknownFile } }; // ! Must be last as a sentinel.
+
+ // Vector of keys in quicktime file in the order in which they appear
+vector <string> ISOMetaKeys;
+
+XMP_Int32 entryCount_dref;
+XMP_Uns16 exif_item_id;
+XMP_Uns16 mime_item_id;
+std::string item_name;
+XMP_Uns32 item_type;
+std::string content_type;
+std::string url_location;
+std::string content_encoding;
+
+
+// File convenience wrappers (now LFA-based) ====================================
+
+// skip forward by <size> bytes and verify not beyond EOF
+void static Skip(LFA_FileRef file, XMP_Int64 size)
+{
+ // assert no more, since LFA_Seek does not return 0 to say o.k., but actual filePos
+ // OLD assertMsg("unexpected end of file", 0 == LFA_Seek (file, size, SEEK_CUR) );
+ LFA_Seek(file, size, SEEK_CUR);
+}
+
+// going back in the file (use positive values!)
+// (yes redundant to above but "makes a better read")
+void static Rewind(LFA_FileRef file, XMP_Int64 size)
+{
+ assertMsg("use positive values", size > 0);
+ LFA_Seek(file, -size, SEEK_CUR); // ditto to above
+}
+
+// overload, no size parameter, rewinds to start
+void static Rewind(LFA_FileRef file)
+{
+ LFA_Seek(file, 0, SEEK_SET);
+}
+
+XMP_Uns32 static Peek32u(LFA_FileRef file, bool bigEndian = false)
+{
+ XMP_Uns32 value = tree->digest32u(file, "", bigEndian);
+ Rewind(file, 4);
+ return value;
+}
+
+
+// =================================================================================================
+
+static XMP_FileFormat
+LookupFileExtMapping(const char * filePath)
+{
+ std::string fileExt;
+ size_t extPos = strlen(filePath);
+ for (--extPos; extPos > 0; --extPos) if (filePath[extPos] == '.') break;
+
+ if (filePath[extPos] != '.') return kXMP_UnknownFile;
+
+ ++extPos;
+ fileExt.assign(&filePath[extPos]);
+ for (size_t i = 0; i < fileExt.size(); ++i) {
+ if (('A' <= fileExt[i]) && (fileExt[i] <= 'Z')) fileExt[i] += 0x20;
+ }
+
+ size_t mapPos;
+ for (mapPos = 0; kFileExtMap[mapPos].ext != 0; ++mapPos) {
+ if (fileExt == kFileExtMap[mapPos].ext) break;
+ }
+
+ return kFileExtMap[mapPos].format;
+
+} // LookupFileExtMapping
+
+ // =================================================================================================
+
+ //*** used by in-RAM code? needs replacement?
+static void
+CaptureFileData(LFA_FileRef file, XMP_Int64 offset, XMP_Uns32 length)
+{
+
+ if (length > sDataMax) {
+ if (sDataPtr != 0) free(sDataPtr);
+ sDataPtr = (XMP_Uns8*)malloc(length);
+ sDataMax = length;
+ }
+
+ if (offset != 0) LFA_Seek(file, (long)offset, SEEK_SET);
+ LFA_Read(file, sDataPtr, length, true);
+ sDataLen = length;
+
+} // CaptureFileData
+
+ // -------------------------------------------------------------------------------------------------
+
+ //*** used by in-RAM code? needs replacement !!!
+static void
+CaptureXMPF(LFA_FileRef file, XMP_Int64 offset, XMP_Uns32 length)
+{
+
+ if (length > sXMPMax) {
+ if (sXMPPtr != 0) free(sXMPPtr);
+ sXMPPtr = (XMP_Uns8*)malloc(length);
+ sXMPMax = length;
+ }
+
+ if (offset != 0) LFA_Seek(file, (long)offset, SEEK_SET);
+ LFA_Read(file, sXMPPtr, length, true);
+ sXMPLen = length;
+ sXMPPos = offset;
+
+} // CaptureXMPF
+
+ // -------------------------------------------------------------------------------------------------
+
+static void
+CaptureXMP(const XMP_Uns8 * xmpPtr, const XMP_Uns32 xmpLen, XMP_Int64 fileOffset)
+{
+
+ if (xmpLen > sXMPMax) {
+ if (sXMPPtr != 0) free(sXMPPtr);
+ sXMPPtr = (XMP_Uns8*)malloc(xmpLen);
+ sXMPMax = xmpLen;
+ }
+
+ memcpy(sXMPPtr, xmpPtr, xmpLen);
+ sXMPLen = xmpLen;
+ sXMPPos = fileOffset;
+
+} // CaptureXMP
+
+ // -------------------------------------------------------------------------------------------------
+
+static void PrintOnlyASCII_8(XMP_Uns8 * strPtr, XMP_Uns32 strLen, bool stopOnNUL = true)
+{
+ //wrapping to QEBuginese
+ // - NB: remainder (zero termination earlier then length) is catered for...
+
+ tree->addComment(convert8Bit(strPtr, stopOnNUL, strLen));
+}
+
+// -------------------------------------------------------------------------------------------------
+
+// this wrap and the LE counterpart can only be inferior, since
+// its always added as a comment, even if value was more appropriate.
+// ==> callers should make use of convert16Bit directly.
+static void PrintOnlyASCII_16BE(XMP_Uns16 * u16Ptr, XMP_Uns32 u16Bytes, bool stopOnNUL = true)
+{
+ tree->addComment(convert16Bit(true, (XMP_Uns8*)u16Ptr, stopOnNUL, u16Bytes));
+} // PrintOnlyASCII_16BE
+
+ // -------------------------------------------------------------------------------------------------
+
+static void PrintOnlyASCII_16LE(XMP_Uns16 * u16Ptr, XMP_Uns32 u16Bytes, bool stopOnNUL = true)
+{
+ tree->addComment(convert16Bit(false, (XMP_Uns8*)u16Ptr, stopOnNUL, u16Bytes));
+} // PrintOnlyASCII_16LE
+
+ // =================================================================================================
+
+static const XMP_Int64 kJPEGMinSize = 4; // At least the SOI and EOI markers.
+static const XMP_Uns8 kJPEGStart[] = { 0xFF, 0xD8, 0xFF }; // 0xFFD8 is SOI, plus 0xFF for next marker.
+
+static const XMP_Int64 kPhotoshopMinSize = 26 + 4 * 4; // At least the file header and 4 section lengths.
+static const XMP_Uns8 kPhotoshopV1Start[] = { 0x38, 0x42, 0x50, 0x53, 0x00, 0x01 }; // 0x38425053 is "8BPS".
+static const XMP_Uns8 kPhotoshopV2Start[] = { 0x38, 0x42, 0x50, 0x53, 0x00, 0x02 };
+
+static const XMP_Int64 kTIFFMinSize = 8 + 2 + 12 + 4; // At least the header plus 1 minimal IFD.
+static const XMP_Uns8 kTIFFBigStart[] = { 0x4D, 0x4D, 0x00, 0x2A }; // 0x4D is 'M', 0x2A is 42.
+static const XMP_Uns8 kTIFFLittleStart[] = { 0x49, 0x49, 0x2A, 0x00 }; // 0x49 is 'I'.
+
+static const XMP_Int64 kJPEG2KMinSize = 12 + 16; // At least the signature and minimal file type boxes.
+static const XMP_Uns8 kJPEG2KStart[] = { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A };
+
+static const XMP_Int64 kPNGMinSize = 8 + (12 + 13) + 12; // At least the file header plus IHDR and IEND chunks.
+static const XMP_Uns8 kPNGStart[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
+
+static const XMP_Int64 kASFMinSize = 16; // ! Not really accurate, but covers the header GUID.
+
+static const XMP_Int64 kRIFFMinSize = 12;
+
+static const XMP_Int64 kPostScriptMinSize = 49;
+
+
+static const XMP_Int64 kInDesignMinSize = 2 * kINDD_PageSize; // Two master pages.
+
+static const XMP_Int64 kISOMediaMinSize = 16; // At least a minimal file type box.
+static const XMP_Uns8 kISOMediaFTyp[] = { 0x66, 0x74, 0x79, 0x70 }; // "ftyp"
+static const XMP_Uns32 kISOTag_ftyp = 0x66747970UL;
+static const XMP_Uns32 kISOBrand_mp41 = 0x6D703431UL;
+static const XMP_Uns32 kISOBrand_mp42 = 0x6D703432UL;
+static const XMP_Uns32 kISOBrand_avc1 = 0x61766331UL;
+static const XMP_Uns32 kISOBrand_f4v = 0x66347620UL;
+static const XMP_Uns32 kISOBrand_isom = 0x69736F6DUL;
+static const XMP_Uns32 kISOBrand_3gp4 = 0x33677034UL;
+static const XMP_Uns32 kISOBrand_3g2a = 0x33673261UL;
+static const XMP_Uns32 kISOBrand_3g2b = 0x33673262UL;
+static const XMP_Uns32 kISOBrand_3g2c = 0x33673263UL;
+static const XMP_Uns32 kISOBrand_mif1 = 0x6D696631UL;
+
+
+static const XMP_Uns32 kQTTag_XMP_ = 0x584D505FUL;
+
+static const XMP_Int64 kSWFMinSize = (8 + 2 + 4 + 2); // Header with minimal rectangle and an End tag.
+
+static const XMP_Int64 kFLVMinSize = 9; // Header with zero length data.
+
+static const XMP_Uns8 kPostScriptStart[] = { 0xC5, 0xD0, 0xD3, 0xC6 };
+
+static XMP_FileFormat
+CheckFileFormat(const char * filePath, XMP_Uns8 * fileContent, XMP_Int64 fileSize)
+{
+ // ! The buffer passed to CheckFileFormat is just the first 4K bytes of the file.
+
+ if ((fileSize >= kJPEGMinSize) && CheckBytes(fileContent, kJPEGStart, 3)) {
+ return kXMP_JPEGFile;
+ }
+
+ if ((fileSize >= kPhotoshopMinSize) &&
+ (CheckBytes(fileContent, kPhotoshopV1Start, 6) || CheckBytes(fileContent, kPhotoshopV2Start, 6))) {
+ return kXMP_PhotoshopFile;
+ }
+
+ if ((fileSize >= kTIFFMinSize) &&
+ (CheckBytes(fileContent, kTIFFBigStart, 4) || CheckBytes(fileContent, kTIFFLittleStart, 4))) {
+ return kXMP_TIFFFile;
+ }
+
+ if ((fileSize >= kJPEG2KMinSize) && CheckBytes(fileContent, kJPEG2KStart, 12)) {
+ return kXMP_JPEG2KFile;
+ }
+
+ if ((fileSize >= kASFMinSize) && CheckBytes(fileContent, &kASF_HeaderGUID, 16)) {
+ return kXMP_WMAVFile;
+ }
+
+ if ((fileSize >= kPNGMinSize) && CheckBytes(fileContent, kPNGStart, 8)) {
+ return kXMP_PNGFile;
+ }
+
+ if ((fileSize >= kRIFFMinSize) && CheckBytes(fileContent, "RIFF", 4)) {
+ if (CheckBytes(fileContent + 8, "AVI ", 4)) return kXMP_AVIFile;
+ if (CheckBytes(fileContent + 8, "WAVE", 4)) return kXMP_WAVFile;
+ }
+
+ if ((fileSize >= kRIFFMinSize) && CheckBytes(fileContent, "RF64", 4)) {
+ if (CheckBytes(fileContent + 8, "WAVE", 4)) return kXMP_WAVFile;
+ }
+
+ if ((fileSize >= kRIFFMinSize) && CheckBytes(fileContent, "FORM", 4)) {
+ if (CheckBytes(fileContent + 8, "AIFF ", 4)) return kXMP_AIFFFile;
+ if (CheckBytes(fileContent + 8, "AIFC", 4)) return kXMP_AIFFFile;
+ }
+
+ if ((fileSize >= kPostScriptMinSize) && CheckBytes(fileContent, kPostScriptStart, 4)) {
+ return kXMP_PostScriptFile;
+ }
+
+ if ((fileSize >= kInDesignMinSize) && CheckBytes(fileContent, kInDesign_MasterPageGUID, kInDesignGUIDSize)) {
+ return kXMP_InDesignFile;
+ }
+
+ if ((fileSize >= kSWFMinSize) &&
+ (CheckBytes(fileContent, "FWS", 3) || CheckBytes(fileContent, "CWS", 3)) &&
+ (fileContent[3] <= 10 /* 2007 latest is 8 */)) {
+ return kXMP_SWFFile;
+ }
+
+ if ((fileSize >= kFLVMinSize) &&
+ CheckBytes(fileContent, "FLV", 3) &&
+ (fileContent[3] <= 10 /* 2007 latest is 1 */)) {
+ return kXMP_FLVFile;
+ }
+
+ if ((fileSize >= kISOMediaMinSize) && CheckBytes(fileContent + 4, kISOMediaFTyp, 4)) {
+
+ XMP_Uns32 ftypLen = GetUns32BE(fileContent);
+ if (ftypLen == 0) ftypLen = fileSize;
+ if ((ftypLen < kISOMediaMinSize) || (ftypLen > fileSize) || (ftypLen > 4096)) return kXMP_UnknownFile;
+
+ XMP_Uns8 * compatPtr = fileContent + 16;
+ XMP_Uns8 * compatEnd = fileContent + ftypLen;
+
+ for (; compatPtr < compatEnd; compatPtr += 4) {
+ XMP_Uns32 compatBrand = GetUns32BE(compatPtr);
+ switch (compatBrand) {
+ case kISOBrand_mp41:
+ case kISOBrand_mp42:
+ case kISOBrand_avc1:
+ case kISOBrand_isom:
+ case kISOBrand_3gp4:
+ case kISOBrand_3g2a:
+ case kISOBrand_3g2b:
+ case kISOBrand_3g2c:
+ return kXMP_MPEG4File;
+ break;
+ case kISOBrand_mif1:
+ return kXMP_HEIFFile;
+ break;
+ default:
+ break;
+
+ }
+
+ }
+
+ }
+
+ if ((fileSize > 30) && CheckBytes(fileContent, "\x50\x4B\x03\x04", 4)) { // "PK 03 04"
+ return kXMP_UCFFile;
+ }
+
+ // ! Do MP3 next to last. It uses the file extension if there is no ID3.
+ if (CheckBytes(fileContent, "ID3", 3) ||
+ (LookupFileExtMapping(filePath) == kXMP_MP3File)) return kXMP_MP3File;
+
+ // ! Do MPEG (MP2) and MOV last. They use just the file extension, not content.
+ if (LookupFileExtMapping(filePath) == kXMP_MPEGFile) return kXMP_MPEGFile;
+ if (LookupFileExtMapping(filePath) == kXMP_MOVFile) return kXMP_MOVFile;
+
+ std::string fileData = (char*)fileContent;
+ if ((fileSize > 30) && (int)fileData.find("<svg") >= 0) {
+ return kXMP_SVGFile;
+ }
+
+ return kXMP_UnknownFile;
+
+} // CheckFileFormat
+
+ // =================================================================================================
+ // DumpXMP
+ // =======
+
+static void
+DumpXMP(XMP_Uns8 * xmpPtr, XMP_Uns32 xmpLen, XMP_Int64 xmpOffset, const char * label)
+{
+ if (xmpOffset <= 0xFFFFFFFFL) {
+ tree->pushNode("XMP");
+ tree->addComment("from %s, offset %u (0x%X), size %d",
+ label, (XMP_Uns32)xmpOffset, (XMP_Uns32)xmpOffset, xmpLen);
+ }
+ else {
+ tree->pushNode("XMP");
+ tree->addComment("from %s, offset %ll (0x%X-%.8X), size %d",
+ label, High32(xmpOffset), Low32(xmpOffset), xmpLen);
+ }
+
+ //bool atStart = true;
+ SXMPMeta xmp((XMP_StringPtr)xmpPtr, xmpLen);
+ xmp.Sort();
+
+ //FNO: could be reactived, but makes the dump naturally very long - harder to read for dev work
+ //xmp.DumpObject( (DumpCallback, &atStart);
+ tree->popNode();
+}
+
+// =================================================================================================
+// DumpXMP
+// =======
+
+// an (old) wrapper for above function relying on static, "global" variables
+static void
+DumpXMP(const char * label)
+{
+ DumpXMP(sXMPPtr, sXMPLen, sXMPPos, label);
+} // DumpXMP
+
+ // =================================================================================================
+ // DumpIPTC
+ // ========
+ //
+ // The IPTC (IIM, NAA) values are in a sequence of "data sets". Each has a 5 byte header followed
+ // by the value. There is no overall length in the sequence itself. Photoshop writes this in TIFF
+ // as LONGs (4 byte chunks), so there might be padding at the end.
+
+static void
+DumpIPTC(XMP_Uns8 * iptcOrigin, XMP_Uns32 iptcLen, XMP_Uns32 fileOffset, const char * label)
+{
+ tree->pushNode("IPTC data");
+ tree->addComment("from %s, offset %d (0x%X), size %d",
+ label, fileOffset, fileOffset, iptcLen);
+
+ // ** Compute and print the MD5 digest.
+ XMP_Uns8 * iptcPtr = iptcOrigin;
+ XMP_Uns8 * iptcEnd = iptcPtr + iptcLen;
+ XMP_Uns8 * valuePtr;
+ XMP_Uns32 valueLen;
+
+ while (iptcPtr < (iptcEnd - 4)) { // ! The -4 is to skip terminal padding.
+ IPTC_DataSet * currDS = (IPTC_DataSet*)iptcPtr;
+ if (currDS->tagMarker != 0x1C) {
+ tree->comment("** invalid IPTC marker **");
+ break;
+ }
+ valuePtr = iptcPtr + 5;
+ valueLen = (currDS->octetCountHigh << 8) + currDS->octetCountLow;
+
+ if ((valueLen >> 15) == 1) {
+ int count = valueLen & 0x7FFF;
+ valueLen = 0;
+ for (int i = 0; i < count; ++i) valueLen = (valueLen << 8) + valuePtr[i];
+ valuePtr += count;
+ }
+
+ XMP_Uns32 dsOffset = fileOffset + (iptcPtr - iptcOrigin);
+
+ //key come here ===================
+ tree->setKeyValue(
+ fromArgs("IPTC:%d:%d", currDS->recordNumber, currDS->dataSetNumber), "");
+ tree->addComment("offset %d (0x%X), size %d", dsOffset, dsOffset, valueLen);
+
+
+ if ((currDS->recordNumber != 1) && (currDS->recordNumber != 2)) {
+
+ //LF only 1:** and 2:** bother us
+
+ }
+ else if (currDS->recordNumber == 1) {
+
+ switch (currDS->dataSetNumber) {
+ case 0:
+ {
+ XMP_Uns16 version = GetUns16BE(valuePtr);
+ tree->addComment("version = 0x%.4X", version);
+ break;
+ }
+ case 90:
+ if (valueLen == 3) {
+ tree->addComment("encoding = 0x%.2X%.2X%.2X", valuePtr[0], valuePtr[1], valuePtr[2]);
+ if (memcmp(valuePtr, "\x1B\x25\x47", 3) == 0) tree->addComment(" (UTF-8)");
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+ else if (currDS->dataSetNumber == 0) {
+
+ XMP_Uns16 version = GetUns16BE(valuePtr);
+ tree->addComment(",Version = 0x%.4X", version);
+
+ }
+ else {
+
+ int ds;
+ for (ds = 0; kDataSetNames[ds].name != 0; ++ds) {
+ if (currDS->dataSetNumber == kDataSetNames[ds].id) break;
+ }
+ if (kDataSetNames[ds].name == 0) {
+ //LF
+ }
+ else {
+ tree->addComment("%s", kDataSetNames[ds].name);
+ tree->changeValue(convert8Bit(valuePtr, false, valueLen));
+ }
+
+ }
+
+ iptcPtr = valuePtr + valueLen;
+
+ }
+
+ //LF
+ if (iptcPtr > iptcEnd) {
+ tree->comment("** Too much IPTC data, delta %d", (long)(iptcEnd - iptcPtr));
+ }
+ else {
+ while ((iptcPtr < iptcEnd) && (*iptcPtr == 0)) ++iptcPtr;
+ if (iptcPtr != iptcEnd) tree->comment("** Too little IPTC data, delta %d", (long)(iptcPtr - iptcEnd));
+ }
+
+ tree->popNode();
+} // DumpIPTC
+
+ // =================================================================================================
+
+static void
+DumpImageResources(const JpegMarkers& psirMarkers, XMP_Uns8 * dataStart, const char * label)
+{
+
+ XMP_Uns32 i = 0, size = psirMarkers.size();
+ std::string combinedPSIRData;
+ for (i = 0; i < size; i++) {
+ combinedPSIRData.append((const char *)psirMarkers[i].jpegMarkerPtr, psirMarkers[i].jpegMarkerLen);
+ }
+
+
+ XMP_Uns8 * psirPtr = (XMP_Uns8 *)combinedPSIRData.data();
+ XMP_Uns8 * psirEnd = psirPtr + combinedPSIRData.size();
+
+ XMP_Uns8 * irPtr;
+ XMP_Uns32 irLen, irOffset; //irType replaced by irTypeStr below
+
+ XMP_Uns8 * iptcPtr = 0;
+ XMP_Uns8 * xmpPtr = 0;
+ XMP_Uns8 * exif1Ptr = 0;
+ XMP_Uns8 * exif3Ptr = 0;
+ XMP_Uns32 iptcLen, xmpLen, exif1Len, exif3Len;
+ XMP_Int32 lastIndexUsed = -1;
+ while (psirPtr < psirEnd) {
+ // calculate fileOffset and psirOrigin
+ size_t currentOffset = (const char *)psirPtr - combinedPSIRData.data();
+ XMP_Uns32 length = 0;
+ for (i = 0; i < size; i++) {
+ length += psirMarkers[i].jpegMarkerLen;
+ if (currentOffset <= length)
+ break;
+ }
+ if (lastIndexUsed != (XMP_Int32)i) {
+ if (lastIndexUsed != -1)
+ tree->popNode();
+ // time to push a new node
+ tree->pushNode("Photoshop Image Resources %d", i + 1);
+ XMP_Uns32 fileOffset = psirMarkers[i].jpegMarkerPtr - dataStart;
+ tree->addComment("from %s, offset %d (0x%X), size %d",
+ label, fileOffset, fileOffset, psirMarkers[i].jpegMarkerLen);
+ lastIndexUsed = i;
+ }
+ XMP_Uns32 fileOffset = psirMarkers[i].jpegMarkerPtr - dataStart;
+ XMP_Uns8 * psirOrigin = psirMarkers[i].jpegMarkerPtr;
+
+ std::string irTypeStr = convert8Bit(psirPtr, false, 4); //get in an endian neutral way
+ XMP_Uns16 irID = GetUns16BE(psirPtr + 4); // The image resource ID.
+
+ const char* irName = (XMP_StringPtr)psirPtr + 6; // A Pascal string.
+ irOffset = 6 + ((*irName + 2) & 0xFFFFFFFE); // Offset to the image resource data length.
+ irLen = GetUns32BE(psirPtr + irOffset);
+ irPtr = psirPtr + irOffset + 4;
+
+ irOffset = fileOffset + ((psirPtr - (XMP_Uns8 *)combinedPSIRData.data()) - (length - psirMarkers[i].jpegMarkerLen));
+
+ if (irTypeStr != "8BIM") {
+ tree->setKeyValue(fromArgs("PSIR:%s:#%u", irTypeStr.c_str(), irID), "");
+ tree->comment("(non-8BIM encountered and tolerated, see bug 1454756)");
+ }
+ else if (irID == kPSIR_IPTC) { //****************
+ tree->setKeyValue("PSIR:IPTC", "");
+ iptcPtr = irPtr;
+ iptcLen = irLen;
+ if (iptcPtr != 0) {
+ XMP_Uns32 offset = fileOffset + ((iptcPtr - (XMP_Uns8 *)combinedPSIRData.data()) - (length - psirMarkers[i].jpegMarkerLen));
+ DumpIPTC(iptcPtr, iptcLen, offset, "PSIR #1028");
+ }
+ }
+ else if (irID == kPSIR_XMP) { //****************
+ tree->setKeyValue("PSIR:XMP", "");
+ xmpPtr = irPtr;
+ xmpLen = irLen;
+ if (xmpPtr != 0) {
+ XMP_Uns32 offset = fileOffset + ((xmpPtr - (XMP_Uns8 *)combinedPSIRData.data()) - (length - psirMarkers[i].jpegMarkerLen));
+ DumpXMP(xmpPtr, xmpLen, offset, "PSIR #1060");
+ }
+ }
+ else if (irID == kPSIR_Exif_1) { //****************
+ tree->setKeyValue("PSIR:Exif-1", "");
+ exif1Ptr = irPtr;
+ exif1Len = irLen;
+ XMP_Uns32 offset = fileOffset + ((exif1Ptr - (XMP_Uns8 *)combinedPSIRData.data()) - (length - psirMarkers[i].jpegMarkerLen));
+ DumpTIFF(exif1Ptr, exif1Len, offset, "PSIR #1058 (Exif 1)", "PSIR:Exif-1");
+ }
+ else if (irID == kPSIR_Exif_3) { //****************
+ tree->setKeyValue("PSIR:Exif-3", "");
+ exif3Ptr = irPtr;
+ exif3Len = irLen;
+ XMP_Uns32 offset = fileOffset + ((exif3Ptr - (XMP_Uns8 *)combinedPSIRData.data()) - (length - psirMarkers[i].jpegMarkerLen));
+ if (exif3Ptr != 0) DumpTIFF(exif3Ptr, exif3Len, offset, "PSIR #1059 (Exif 3)", "PSIR:Exif-3");
+ }
+ else if (irID == kPSIR_IPTC_Digest) {
+ tree->setKeyValue("PSIR:IPTC digest",
+ fromArgs("%.8X-%.8X-%.8X-%.8X",
+ GetUns32BE(irPtr),
+ GetUns32BE(irPtr + 4),
+ GetUns32BE(irPtr + 8),
+ GetUns32BE(irPtr + 12))
+ );
+ }
+ else if (irID == kPSIR_CopyrightFlag) {
+ bool copyrighted = (*irPtr != 0);
+ tree->setKeyValue("PSIR:copyrighted", (copyrighted ? "yes" : "no"));
+ }
+ else if (irID == kPSIR_CopyrightURL) {
+ tree->setKeyValue("PSIR:copyright URL", convert8Bit(irPtr, true, irLen));
+ }
+ else if (irID == kPSIR_OldCaption) {
+ tree->setKeyValue("PSIR:old caption", convert8Bit(irPtr, true, irLen));
+ }
+ else if (irID == kPSIR_PrintCaption) {
+ tree->comment("** obsolete print caption **");
+ }
+ else {
+ tree->setKeyValue(
+ fromArgs("PSIR:%s:#%d", irTypeStr.c_str(), irID),
+ ""
+ );
+ }
+ if (irOffset + irLen > (psirMarkers[i].jpegMarkerPtr - dataStart) + psirMarkers[i].jpegMarkerLen) {
+ //merged from two markers
+ tree->addComment("offset %d (0x%X), size %d - split in multiple markers", irOffset, irOffset, irLen);
+ }
+ else {
+ tree->addComment("offset %d (0x%X), size %d", irOffset, irOffset, irLen);
+ }
+ if (*irName != 0) tree->addComment("\"%.*s\"", (int)(*irName), (irName + 1));
+ psirPtr = irPtr + ((irLen + 1) & 0xFFFFFFFE); // Round the length to be even.
+ } //while-loop
+
+ if (psirPtr != psirEnd) {
+ tree->addComment("** Unexpected end of image resources, delta %d", (long)(psirPtr - psirEnd));
+ }
+
+ //NB: dump routines moved up into if-else's
+ tree->popNode();
+} // DumpImageResources
+
+ // =================================================================================================
+
+static void
+DumpImageResources(XMP_Uns8 * psirOrigin, XMP_Uns32 psirLen, XMP_Uns32 fileOffset, const char * label)
+{
+ tree->pushNode("Photoshop Image Resources");
+ tree->addComment("from %s, offset %d (0x%X), size %d",
+ label, fileOffset, fileOffset, psirLen);
+
+ XMP_Uns8 * psirPtr = psirOrigin;
+ XMP_Uns8 * psirEnd = psirPtr + psirLen;
+ XMP_Uns8 * irPtr;
+ XMP_Uns32 irLen, irOffset; //irType replaced by irTypeStr below
+
+ XMP_Uns8 * iptcPtr = 0;
+ XMP_Uns8 * xmpPtr = 0;
+ XMP_Uns8 * exif1Ptr = 0;
+ XMP_Uns8 * exif3Ptr = 0;
+ XMP_Uns32 iptcLen, xmpLen, exif1Len, exif3Len;
+
+ while (psirPtr < psirEnd) {
+ std::string irTypeStr = convert8Bit(psirPtr, false, 4); //get in an endian neutral way
+ XMP_Uns16 irID = GetUns16BE(psirPtr + 4); // The image resource ID.
+
+ const char* irName = (XMP_StringPtr)psirPtr + 6; // A Pascal string.
+ irOffset = 6 + ((*irName + 2) & 0xFFFFFFFE); // Offset to the image resource data length.
+ irLen = GetUns32BE(psirPtr + irOffset);
+ irPtr = psirPtr + irOffset + 4;
+
+ irOffset = fileOffset + (psirPtr - psirOrigin);
+
+ if (irTypeStr != "8BIM") {
+ tree->setKeyValue(fromArgs("PSIR:%s:#%u", irTypeStr.c_str(), irID), "");
+ tree->comment("(non-8BIM encountered and tolerated, see bug 1454756)");
+ }
+ else if (irID == kPSIR_IPTC) { //****************
+ tree->setKeyValue("PSIR:IPTC", "");
+ iptcPtr = irPtr;
+ iptcLen = irLen;
+ if (iptcPtr != 0) DumpIPTC(iptcPtr, iptcLen, (fileOffset + (iptcPtr - psirOrigin)), "PSIR #1028");
+ }
+ else if (irID == kPSIR_XMP) { //****************
+ tree->setKeyValue("PSIR:XMP", "");
+ xmpPtr = irPtr;
+ xmpLen = irLen;
+ if (xmpPtr != 0) DumpXMP(xmpPtr, xmpLen, (fileOffset + (xmpPtr - psirOrigin)), "PSIR #1060");
+ }
+ else if (irID == kPSIR_Exif_1) { //****************
+ tree->setKeyValue("PSIR:Exif-1", "");
+ exif1Ptr = irPtr;
+ exif1Len = irLen;
+ DumpTIFF(exif1Ptr, exif1Len, (fileOffset + (exif1Ptr - psirOrigin)), "PSIR #1058 (Exif 1)", "PSIR:Exif-1");
+ }
+ else if (irID == kPSIR_Exif_3) { //****************
+ tree->setKeyValue("PSIR:Exif-3", "");
+ exif3Ptr = irPtr;
+ exif3Len = irLen;
+ if (exif3Ptr != 0) DumpTIFF(exif3Ptr, exif3Len, (fileOffset + (exif3Ptr - psirOrigin)), "PSIR #1059 (Exif 3)", "PSIR:Exif-3");
+ }
+ else if (irID == kPSIR_IPTC_Digest) {
+ tree->setKeyValue("PSIR:IPTC digest",
+ fromArgs("%.8X-%.8X-%.8X-%.8X",
+ GetUns32BE(irPtr),
+ GetUns32BE(irPtr + 4),
+ GetUns32BE(irPtr + 8),
+ GetUns32BE(irPtr + 12))
+ );
+ }
+ else if (irID == kPSIR_CopyrightFlag) {
+ bool copyrighted = (*irPtr != 0);
+ tree->setKeyValue("PSIR:copyrighted", (copyrighted ? "yes" : "no"));
+ }
+ else if (irID == kPSIR_CopyrightURL) {
+ tree->setKeyValue("PSIR:copyright URL", convert8Bit(irPtr, true, irLen));
+ }
+ else if (irID == kPSIR_OldCaption) {
+ tree->setKeyValue("PSIR:old caption", convert8Bit(irPtr, true, irLen));
+ }
+ else if (irID == kPSIR_PrintCaption) {
+ tree->comment("** obsolete print caption **");
+ }
+ else {
+ tree->setKeyValue(
+ fromArgs("PSIR:%s:#%d", irTypeStr.c_str(), irID),
+ ""
+ );
+ }
+ tree->addComment("offset %d (0x%X), size %d", irOffset, irOffset, irLen);
+ if (*irName != 0) tree->addComment("\"%.*s\"", (int)(*irName), (irName + 1));
+ psirPtr = irPtr + ((irLen + 1) & 0xFFFFFFFE); // Round the length to be even.
+ } //while-loop
+
+ if (psirPtr != psirEnd) {
+ tree->addComment("** Unexpected end of image resources, delta %d", (long)(psirPtr - psirEnd));
+ }
+
+ //NB: dump routines moved up into if-else's
+
+ tree->popNode();
+} // DumpImageResources
+
+ // =================================================================================================
+
+static void
+DumpOneIFD(int ifdIndex, XMP_Uns8 * ifdPtr, XMP_Uns8 * endPtr,
+ XMP_Uns8 * tiffContent, XMP_Uns32 fileOffset, const char * label, std::string path)
+{
+ XMP_Uns8 * exifPtr = 0;
+ XMP_Uns8 * gpsPtr = 0;
+ XMP_Uns8 * interopPtr = 0;
+ XMP_Uns8 * makerNotePtr = 0;
+ XMP_Uns8 * psirPtr = 0;
+ XMP_Uns8 * iptcPtr = 0;
+ XMP_Uns8 * xmpPtr = 0;
+ XMP_Uns32 psirLen = 0;
+ XMP_Uns32 iptcLen = 0;
+ XMP_Uns32 xmpLen = 0;
+
+ XMP_Uns32 ifdOffset = ifdPtr - tiffContent;
+ XMP_Uns16 fieldCount = TIFF_GetUns16(ifdPtr);
+ XMP_Uns32 ifdLen = 2 + (12 * fieldCount) + 4;
+ XMP_Uns32 nextIFD = TIFF_GetUns32(ifdPtr + ifdLen - 4);
+
+ tree->pushNode("TIFF IFD #%d from %s", ifdIndex, label);
+ tree->addComment("offset %d (0x%X), tag count %d",
+ (ifdOffset + fileOffset), (ifdOffset + fileOffset), fieldCount);
+
+ if (nextIFD == 0) {
+ tree->comment("end of IFD chain");
+ }
+ else {
+ tree->comment("next IFD offset %d (0x%X)", (nextIFD + fileOffset), (nextIFD + fileOffset));
+ }
+
+ XMP_Uns16 prevTag = 0;
+ XMP_Uns8 * fieldPtr = tiffContent + ifdOffset + 2;
+
+ if (!path.empty())
+ path.append("/");
+
+ path.append(fromArgs("IFD%d", ifdIndex));
+
+ for (int i = 0; i < fieldCount; ++i, fieldPtr += 12) {
+
+ XMP_Uns16 fieldTag = TIFF_GetUns16(fieldPtr);
+ XMP_Uns16 fieldType = TIFF_GetUns16(fieldPtr + 2);
+ XMP_Uns32 valueCount = TIFF_GetUns32(fieldPtr + 4);
+ XMP_Uns32 valueOffset = TIFF_GetUns32(fieldPtr + 8);
+
+ XMP_Uns8 * valuePtr = ifdPtr - ifdOffset + valueOffset;
+ XMP_Uns32 valueLen = 0;
+ if (fieldType < kTIFF_TypeEnd) valueLen = valueCount * sTIFF_TypeSizes[fieldType];
+ if (valueLen <= 4) valuePtr = fieldPtr + 8;
+
+ //===================== adding key here
+ tree->setKeyValue(fromArgs("%s/TIFF:%d", path.c_str(), fieldTag));
+
+ if ((fieldType < 1) || (fieldType >= kTIFF_TypeEnd)) {
+ tree->addComment("type %d", fieldType);
+ }
+ else {
+ tree->addComment("%s", sTIFF_TypeNames[fieldType]);
+ }
+
+ tree->addComment("count %d, value size %d", valueCount, valueLen);
+
+ if (valueLen > 4) {
+ tree->addComment("value offset %d (0x%X)", (valueOffset + fileOffset), (valueOffset + fileOffset));
+ }
+ else {
+ XMP_Uns32 rawValue = GetUns32BE(fieldPtr + 8);
+ tree->addComment("value in IFD (0x%.8X)", rawValue);
+ }
+
+ if (fieldTag == kTIFF_Exif) {
+ tree->addComment("Exif IFD offset");
+ exifPtr = tiffContent + TIFF_GetUns32(valuePtr); // Value is Exif IFD offset.
+ }
+ else if (fieldTag == kTIFF_GPS) {
+ tree->addComment("GPS IFD offset");
+ gpsPtr = tiffContent + TIFF_GetUns32(valuePtr); // Value is GPS IFD offset.
+ }
+ else if (fieldTag == kTIFF_Interop) {
+ tree->addComment("Interoperability IFD offset");
+ interopPtr = tiffContent + TIFF_GetUns32(valuePtr); // Value is Interoperability IFD offset.
+ }
+ else if (fieldTag == kTIFF_MakerNote) { // Decide if the Maker Note might be formatted as an IFD.
+ tree->addComment("Maker Note");
+ XMP_Uns32 itemCount = (valueLen - 6) / 12;
+ if ((valueLen >= 18) && (valueLen == (6 + itemCount * 12)) &&
+ (itemCount == TIFF_GetUns16(valuePtr)) &&
+ (TIFF_GetUns32(valuePtr + 2 + (12 * itemCount)) == 0)) {
+ makerNotePtr = valuePtr;
+ }
+ }
+ else if (fieldTag == kTIFF_PSIR) {
+ tree->addComment("PSIR");
+ psirPtr = valuePtr;
+ psirLen = valueLen;
+ }
+ else if (fieldTag == kTIFF_IPTC) {
+ tree->addComment("IPTC");
+ iptcPtr = valuePtr;
+ iptcLen = valueLen;
+ }
+ else if (fieldTag == kTIFF_XMP) {
+ tree->addComment("XMP");
+ if (fieldType == kTIFF_ASCII) fieldType = kTIFF_Uns8; // Avoid displaying the raw packet for mis-typed XMP.
+ xmpPtr = valuePtr;
+ xmpLen = valueLen;
+ }
+ else {
+ for (int j = 0; sTIFF_TagNames[j].tag != 0; ++j) {
+ if (sTIFF_TagNames[j].tag == fieldTag) {
+ tree->addComment("%s", sTIFF_TagNames[j].name);
+ break;
+ }
+ }
+ }
+
+ XMP_Uns8 value8;
+ XMP_Uns16 value16;
+ XMP_Uns32 value32;
+ XMP_Uns32 denom;
+ std::string tempStr;
+ char cs[31];
+
+ switch (fieldType) {
+
+ case kTIFF_Uns8:
+ if (valueCount == 1) {
+ value8 = *valuePtr;
+ tree->addComment("hex value = 0x%.2X", value8);
+ tree->changeValue("%u", value8);
+ }
+ break;
+
+ case kTIFF_ASCII:
+ tree->changeValue(convert8Bit(valuePtr, false /* internal NULs OK*/, valueLen));
+ break;
+
+ case kTIFF_Uns16:
+ if (valueCount == 1) {
+ value16 = TIFF_GetUns16(valuePtr);
+ tree->addComment("hex value = 0x%.4X", value16);
+ tree->changeValue("%u", value16);
+ }
+ break;
+
+ case kTIFF_Uns32:
+ if (valueCount == 1) {
+ value32 = TIFF_GetUns32(valuePtr);
+ tree->addComment("hex value = 0x%.8X", value32);
+ tree->changeValue("%u", value32);
+ }
+ break;
+
+ case kTIFF_URational:
+ for (unsigned int j = 0; j < valueCount; j++) {
+ value32 = TIFF_GetUns32(valuePtr + (j * 8));
+ denom = TIFF_GetUns32(valuePtr + (j * 8) + 4);
+ snprintf(cs, 30, "%u", value32);
+ tempStr += cs;
+ tempStr += "/";
+ snprintf(cs, 30, "%u", denom);
+ tempStr += cs;
+ if (j < valueCount - 1)
+ tempStr += ";";
+ }
+ if (tempStr.length() > 0)
+ tree->changeValue(tempStr);
+ break;
+
+ case kTIFF_Int8:
+ if (valueCount == 1) {
+ value8 = *valuePtr;
+ //fno: show the hex value unsigned (memory representation) and the decimal signed
+ tree->addComment("hex value 0x%.2X", value8);
+ tree->changeValue("%d", *((XMP_Int8*)&value8));
+ }
+ break;
+
+ case kTIFF_Undef8:
+ if (valueCount == 1) {
+ value8 = *valuePtr;
+ tree->changeValue("0x%.2X", value8);
+ }
+ else if (fieldTag == 36864) { // ExifVersion
+ tree->changeValue("%.*s", valueCount, valuePtr);
+ }
+ else if (fieldTag == 37510) { // UserComment
+ XMP_Uns8 * encPtr = valuePtr;
+ valuePtr += 8;
+ valueCount -= 8;
+ sprintf(cs, "encoding = %.8s", encPtr);
+ tempStr += cs;
+ if (!CheckBytes(encPtr, "UNICODE\0", 8)) {
+ tree->changeValue(convert8Bit(valuePtr, false, valueCount));
+ }
+ else {
+ bool doBE = beTIFF;
+ if (CheckBytes(valuePtr, "\xFE\xFF", 2)) {
+ doBE = true;
+ valuePtr += 2;
+ valueCount -= 2;
+ tempStr += ", BE BOM";
+ }
+ if (CheckBytes(valuePtr, "\xFF\xFE", 2)) {
+ doBE = false;
+ valuePtr += 2;
+ valueCount -= 2;
+ tempStr += ", LE BOM";
+ }
+ if (doBE) {
+ tree->changeValue(convert16Bit(true, (XMP_Uns8*)valuePtr, false, valueCount));
+ //PrintOnlyASCII_16BE ( (XMP_Uns16*)valuePtr, valueCount, ", value =", false /* ! stopOnNUL */ );
+ }
+ else {
+ tree->changeValue(convert16Bit(false, (XMP_Uns8*)valuePtr, false, valueCount));
+ //PrintOnlyASCII_16LE ( (XMP_Uns16*)valuePtr, valueCount, ", value =", false /* ! stopOnNUL */ );
+ }
+ }
+ }
+
+ if (tempStr.length() > 0)
+ tree->addComment(tempStr);
+ break;
+
+ case kTIFF_Int16:
+ if (valueCount == 1) {
+ value16 = TIFF_GetUns16(valuePtr);
+ tree->changeValue("%d (0x%.4X)", *((XMP_Int16*)&value16), value16);
+ }
+ break;
+
+ case kTIFF_Int32:
+ if (valueCount == 1) {
+ value32 = TIFF_GetUns32(valuePtr);
+ tree->changeValue("%d (0x%.8X)", *((XMP_Int32*)&value32), value32);
+ }
+ break;
+
+ case kTIFF_SRational:
+ if (valueCount == 1) {
+ value32 = TIFF_GetUns32(valuePtr);
+ denom = TIFF_GetUns32(valuePtr + 4);
+ tree->changeValue("%d/%d", *((XMP_Int32*)&value32), *((XMP_Int32*)&denom));
+ }
+ break;
+
+ case kTIFF_Float:
+ break;
+
+ case kTIFF_Double:
+ break;
+
+ case kTIFF_IFD:
+ if (valueCount == 1) {
+ value32 = TIFF_GetUns32(valuePtr);
+ tree->addComment("hex value = 0x%.8X", value32);
+ tree->changeValue("%u", value32);
+ }
+ break;
+
+ default:
+ tree->addComment("** unknown type **");
+ break;
+
+ }
+
+ if (fieldTag == prevTag) {
+ tree->addComment("** Repeated tag **");
+ }
+ else if (fieldTag < prevTag) {
+ tree->addComment("** Out of order tag **");
+ }
+
+ prevTag = fieldTag;
+
+ }
+
+ if (exifPtr != 0) {
+ DumpIFDChain(exifPtr, endPtr, tiffContent,
+ (fileOffset + (exifPtr - tiffContent)), "TIFF tag #34665 (Exif IFD)", path + "/TIFF:34665");
+ }
+
+ if (gpsPtr != 0) {
+ DumpIFDChain(gpsPtr, endPtr, tiffContent,
+ (fileOffset + (gpsPtr - tiffContent)), "TIFF tag #34853 (GPS Info IFD)", path + "/TIFF:34853");
+ }
+
+ if (interopPtr != 0) {
+ DumpIFDChain(interopPtr, endPtr, tiffContent,
+ (fileOffset + (interopPtr - tiffContent)), "TIFF tag #40965 (Interoperability IFD)", path + "/TIFF:40965");
+ }
+
+ if (makerNotePtr != 0) {
+ DumpIFDChain(makerNotePtr, endPtr, tiffContent,
+ (fileOffset + (makerNotePtr - tiffContent)), "TIFF tag #37500 (Maker Note)", path + "/TIFF:37500");
+ }
+
+ if (iptcPtr != 0) {
+ DumpIPTC(iptcPtr, iptcLen, (fileOffset + (iptcPtr - tiffContent)), "TIFF tag #33723");
+ }
+
+ if (psirPtr != 0) {
+ DumpImageResources(psirPtr, psirLen, (fileOffset + (psirPtr - tiffContent)), "TIFF tag #34377");
+ }
+
+ if (xmpPtr != 0) {
+ DumpXMP(xmpPtr, xmpLen, (fileOffset + (xmpPtr - tiffContent)), "TIFF tag #700");
+ }
+
+ tree->popNode();
+} // DumpOneIFD
+
+ // =================================================================================================
+
+
+static void
+DumpIFDChain(XMP_Uns8 * startPtr, XMP_Uns8 * endPtr,
+ XMP_Uns8 * tiffContent, XMP_Uns32 fileOrigin, const char * label, std::string path, bool isHeaderAbsent)
+{
+ XMP_Uns8 * ifdPtr = startPtr;
+ XMP_Uns32 ifdOffset = startPtr - tiffContent;
+
+ if (isHeaderAbsent) // It's a kind of hack to iterate all the ifdboxes at least once.
+ ifdOffset = 1;
+
+ for (size_t ifdIndex = 0; ifdOffset != 0; ++ifdIndex) {
+
+ if ((ifdPtr < tiffContent) || (ifdPtr >= endPtr)) {
+ ifdOffset = fileOrigin + (ifdPtr - tiffContent);
+ tree->comment("** Invalid IFD offset, %d (0x%X) tree.", ifdOffset, ifdOffset);
+ return;
+ }
+
+ XMP_Uns16 fieldCount = TIFF_GetUns16(ifdPtr);
+ DumpOneIFD(ifdIndex, ifdPtr, endPtr, tiffContent, fileOrigin, label, path);
+ ifdOffset = TIFF_GetUns32(ifdPtr + 2 + (12 * fieldCount));
+ ifdPtr = tiffContent + ifdOffset;
+
+ }
+
+} // DumpIFDChain
+
+ // =================================================================================================
+
+static void
+DumpTIFF(XMP_Uns8 * tiffContent, XMP_Uns32 tiffLen, XMP_Uns32 fileOffset, const char * label, std::string path, bool isHeaderAbsent)
+{
+ tree->pushNode("TIFF content from %s", label);
+ // ! TIFF can be nested because of the Photoshop 6 weiredness. Save and restore the procs.
+ GetUns16_Proc save_GetUns16 = TIFF_GetUns16;
+ GetUns32_Proc save_GetUns32 = TIFF_GetUns32;
+ GetUns64_Proc save_GetUns64 = TIFF_GetUns64;
+
+ XMP_Uns32 ifdOffset = 0;
+
+ if (!isHeaderAbsent)
+ {
+ if (CheckBytes(tiffContent, "II\x2A\x00", 4)) {
+ beTIFF = false;
+ TIFF_GetUns16 = GetUns16LE;
+ TIFF_GetUns32 = GetUns32LE;
+ TIFF_GetUns64 = GetUns64LE;
+ tree->addComment("Little endian ");
+ }
+ else if (CheckBytes(tiffContent, "MM\x00\x2A", 4)) {
+ beTIFF = true;
+ TIFF_GetUns16 = GetUns16BE;
+ TIFF_GetUns32 = GetUns32BE;
+ TIFF_GetUns64 = GetUns64BE;
+ tree->addComment("Big endian ");
+ }
+ else {
+ tree->comment("** Missing TIFF image file header tree.");
+ return;
+ }
+
+ tree->addComment("TIFF from %s, offset %d (0x%X), size %d", label, fileOffset, fileOffset, tiffLen);
+
+ ifdOffset = TIFF_GetUns32(tiffContent + 4);
+ }
+ else
+ {
+ beTIFF = false;
+ TIFF_GetUns16 = GetUns16LE;
+ TIFF_GetUns32 = GetUns32LE;
+ TIFF_GetUns64 = GetUns64LE;
+ tree->addComment("Little endian ");
+ }
+
+ DumpIFDChain(tiffContent + ifdOffset, tiffContent + tiffLen, tiffContent, fileOffset, label, path, isHeaderAbsent);
+
+ TIFF_GetUns16 = save_GetUns16;
+ TIFF_GetUns32 = save_GetUns32;
+ TIFF_GetUns64 = save_GetUns64;
+
+ tree->popNode();
+} // DumpTIFF
+
+
+ // =================================================================================================
+
+static void DumpTIFF(const JpegMarkers& exifMarkers, XMP_Uns8 * dataStart, const char * label, std::string path)
+{
+ XMP_Uns32 i = 0, size = exifMarkers.size();
+ std::string combinedExifData;
+ tree->pushNode("Combined EXIF Markers from %s", path.c_str());
+ for (i = 0; i < size; i++) {
+ tree->pushNode("EXIF Marker %d", i + 1);
+ tree->addComment("offset %d (0x%X), size %d", exifMarkers[i].jpegMarkerPtr - dataStart,
+ exifMarkers[i].jpegMarkerPtr - dataStart, exifMarkers[i].jpegMarkerLen);
+ combinedExifData.append((const char *)exifMarkers[i].jpegMarkerPtr, exifMarkers[i].jpegMarkerLen);
+ tree->popNode();
+ }
+
+ DumpTIFF((XMP_Uns8 *)combinedExifData.data(), combinedExifData.length(), exifMarkers[0].jpegMarkerPtr - dataStart, label, path);
+
+ tree->popNode();
+} // DumpTIFF
+
+ // =================================================================================================
+
+static void
+DumpPhotoshop(XMP_Uns8 * psdContent, XMP_Uns32 psdLen)
+{
+ psdLen = psdLen; // Avoid unused parameter warning.
+
+ XMP_Uns32 psirOffset = 26 + 4 + GetUns32BE(psdContent + 26);
+ XMP_Uns8 * psirSect = psdContent + psirOffset;
+ XMP_Uns8 * psirPtr = psirSect + 4;
+ XMP_Uns32 psirLen = GetUns32BE(psirSect);
+
+ DumpImageResources(psirPtr, psirLen, (psirPtr - psdContent), "Photoshop file");
+
+} // DumpPhotoshop
+
+ // =================================================================================================
+
+static void
+DumpJPEG(XMP_Uns8 * jpegContent, XMP_Uns32 jpegLen)
+{
+ XMP_Uns8 * endPtr = jpegContent + jpegLen;
+ XMP_Uns8 * segPtr = jpegContent;
+ XMP_Uns32 segOffset;
+
+ XMP_Uns8 * xmpPtr = 0;
+ XMP_Uns16 xmpLen = 0;
+
+ JpegMarkers psirMarkers, exifMarkers;
+
+ while (segPtr < endPtr) { // ----------------------------------------------------------------
+
+ XMP_Uns16 segMark = GetUns16BE(segPtr);
+ if (segMark == 0xFFFF) {
+ segPtr += 1; // Skip leading 0xFF pad byte.
+ continue;
+ }
+
+ XMP_Uns16 minorKind = segMark & 0x000F;
+ segOffset = segPtr - jpegContent;
+
+ tree->pushNode("JPEG:%.4X", segMark);
+ tree->addComment("offset %d (0x%X)", segOffset, segOffset);
+
+ if (((segMark >> 8) != 0xFF) || (segMark == 0xFF00)) {
+ tree->addComment("** invalid JPEG marker **");
+ tree->popNode();
+ break;
+ }
+
+ // Check for standalone markers first, only fetch the length for marker segments.
+
+ if (segMark == 0xFF01) {
+ tree->addComment("** TEM **");
+ segPtr += 2; // A standalone marker.
+ tree->popNode();
+ continue;
+ }
+ else if ((0xFFD0 <= segMark) && (segMark <= 0xFFD7)) {
+ tree->addComment(fromArgs("RST%d ** unexpected **", minorKind));
+ segPtr += 2; // A standalone marker.
+ tree->popNode();
+ continue;
+ }
+ else if (segMark == 0xFFD8) {
+ tree->addComment("SOI");
+ segPtr += 2; // A standalone marker.
+ tree->popNode();
+ continue;
+ }
+ else if (segMark == 0xFFD9) {
+ tree->addComment("EOI");
+ segPtr += 2; // A standalone marker.
+ tree->popNode();
+ break; // Exit on EOI.
+ }
+
+ XMP_Uns16 segLen = GetUns16BE(segPtr + 2);
+
+ // figure out Exif vs PSIR vs XMP
+ if ((0xFFE0 <= segMark) && (segMark <= 0xFFEF)) {
+ const char* segName = (const char *)(segPtr + 4);
+ tree->addComment(fromArgs("size %d, APP%d, \"%s\"", segLen, minorKind, segName));
+ if ((minorKind == 1) &&
+ ((memcmp(segName, "Exif\0\0", 6) == 0) || (memcmp(segName, "Exif\0\xFF", 6) == 0))) {
+ tree->addComment("EXIF");
+ tree->changeValue("EXIF");
+ JpegMarker exifMarker;
+ exifMarker.jpegMarkerPtr = segPtr + 4 + 6;
+ exifMarker.jpegMarkerLen = segLen - 2 - 6;
+ exifMarkers.push_back(exifMarker);
+ }
+ else if ((minorKind == 13) && (strcmp(segName, "Photoshop 3.0") == 0)) {
+ tree->addComment("PSIR");
+ tree->changeValue("PSIR");
+ JpegMarker psirMarker;
+ psirMarker.jpegMarkerPtr = segPtr + 4 + strlen(segName) + 1;
+ psirMarker.jpegMarkerLen = (XMP_Uns16)(segLen - 2 - strlen(segName) - 1);
+ psirMarkers.push_back(psirMarker);
+ }
+ else if ((minorKind == 1) && (strcmp(segName, "http://ns.adobe.com/xap/1.0/") == 0)) {
+ tree->addComment("XMP");
+ tree->changeValue("XMP");
+ xmpPtr = segPtr + 4 + strlen(segName) + 1;
+ xmpLen = (XMP_Uns16)(segLen - 2 - strlen(segName) - 1);
+ }
+ segPtr += 2 + segLen;
+ tree->popNode();
+ continue;
+ }
+
+ if (segMark == 0xFFDA) {
+ tree->addComment(fromArgs("size %d, SOS", segLen));
+ segPtr += 2 + segLen; // Skip the SOS marker segment itself
+ long rstCount = 0;
+ while (segPtr < endPtr) { // Skip the entropy-coded data and RSTn markers.
+ if (*segPtr != 0xFF) {
+ segPtr += 1; // General data byte.
+ }
+ else {
+ segMark = GetUns16BE(segPtr);
+ if (segMark == 0xFF00) {
+ segPtr += 2; // Padded 0xFF data byte.
+ }
+ else if ((segMark < 0xFFD0) || (segMark > 0xFFD7)) {
+ segLen = 0;
+ segPtr -= 2; // Prepare for the increment in the outer loop.
+ break; // Exit, non-RSTn marker.
+ }
+ else {
+ ++rstCount;
+ segPtr += 2;
+ }
+ }
+ }
+ tree->addComment(fromArgs("%d restart markers", rstCount));
+
+ segPtr += 2 + segLen;
+ tree->popNode();
+ continue;
+ }
+
+
+ if ((0xFF02 <= segMark) && (segMark <= 0xFFBF)) {
+ tree->addComment(fromArgs("size %d, ** RES **", segLen));
+ }
+ else if ((0xFFC0 <= segMark) && (segMark <= 0xFFC3)) {
+ tree->addComment(fromArgs("size %d, SOF%d", segLen, minorKind));
+ }
+ else if (segMark == 0xFFC4) {
+ tree->addComment(fromArgs("size %d, DHT", segLen));
+ }
+ else if ((0xFFC5 <= segMark) && (segMark <= 0xFFC7)) {
+ tree->addComment(fromArgs("size %d, SOF%d", segLen, minorKind));
+ }
+ else if (segMark == 0xFFC8) {
+ tree->addComment(fromArgs("size %d, JPG", segLen));
+ }
+ else if ((0xFFC9 <= segMark) && (segMark <= 0xFFCB)) {
+ tree->addComment(fromArgs("size %d, SOF%d", segLen, minorKind));
+ }
+ else if (segMark == 0xFFCC) {
+ tree->addComment(fromArgs("size %d, DAC", segLen));
+ }
+ else if ((0xFFCD <= segMark) && (segMark <= 0xFFCF)) {
+ tree->addComment(fromArgs("size %d, SOF%d", segLen, minorKind));
+ }
+ else if (segMark == 0xFFDB) {
+ tree->addComment(fromArgs("size %d, DQT", segLen));
+ }
+ else if (segMark == 0xFFDC) {
+ tree->addComment(fromArgs("size %d, DNL", segLen));
+ }
+ else if (segMark == 0xFFDD) {
+ tree->addComment(fromArgs("size %d, DRI", segLen));
+ }
+ else if (segMark == 0xFFDE) {
+ tree->addComment(fromArgs("size %d, DHP", segLen));
+ }
+ else if (segMark == 0xFFDF) {
+ tree->addComment(fromArgs("size %d, EXP", segLen));
+ }
+ else if ((0xFFF0 <= segMark) && (segMark <= 0xFFFD)) {
+ tree->addComment(fromArgs("size %d, JPG%d", segLen, minorKind));
+ }
+ else if (segMark == 0xFFFE) {
+ tree->addComment(fromArgs("size %d, COM", segLen));
+ }
+ else {
+ tree->addComment("** UNRECOGNIZED MARKER **");
+ }
+
+ segPtr += 2 + segLen;
+
+ tree->popNode();
+ } // ------------------------------------------------------------------------------------
+
+ if (segPtr != endPtr) {
+ segOffset = segPtr - jpegContent;
+ tree->addComment(fromArgs(
+ "** Unexpected end of JPEG markers at offset %d (0x%X), delta %d tree.",
+ segOffset, segOffset, (long)(endPtr - segPtr)
+ ));
+ }
+
+ if (exifMarkers.size() > 0) DumpTIFF(exifMarkers, jpegContent, "JPEG Exif APP1", "JPEG:APP1");
+ if (psirMarkers.size() > 0) DumpImageResources(psirMarkers, jpegContent, "JPEG Photoshop APP13");
+ if (xmpPtr != 0) DumpXMP(xmpPtr, xmpLen, (xmpPtr - jpegContent), "JPEG XMP APP1");
+
+} // DumpJPEG
+
+ // =================================================================================================
+ //#if !IOS_ENV
+static const XMP_Uns8 kUUID_XMP[16] =
+{ 0xBE, 0x7A, 0xCF, 0xCB, 0x97, 0xA9, 0x42, 0xE8, 0x9C, 0x71, 0x99, 0x94, 0x91, 0xE3, 0xAF, 0xAC };
+/*#else
+static const XMP_Uns8 kUUID_XMP[16] =
+{ 0xFFFFFFBE, 0x0000007A, 0xFFFFFFCF, 0xFFFFFFCB, 0xFFFFFF97, 0xFFFFFFA9, 0x00000042, 0xFFFFFFE8, 0xFFFFFF9C, 0x00000071, 0xFFFFFF99, 0xFFFFFF94, 0xFFFFFF91, 0xFFFFFFE3, 0xFFFFFFAF, 0xFFFFFFAC };
+#endif */
+static const XMP_Uns8 kUUID_Exif[16] =
+{ 0x05, 0x37, 0xCD, 0xAB, 0x9D, 0x0C, 0x44, 0x31, 0xA7, 0x2A, 0xFA, 0x56, 0x1F, 0x2A, 0x11, 0x3E };
+static const XMP_Uns8 kUUID_IPTC[16] =
+{ 0x09, 0xA1, 0x4E, 0x97, 0xC0, 0xB4, 0x42, 0xE0, 0xBE, 0xBF, 0x36, 0xDF, 0x6F, 0x0C, 0xE3, 0x6F };
+static const XMP_Uns8 kUUID_PSIR[16] =
+{ 0x2C, 0x4C, 0x01, 0x00, 0x85, 0x04, 0x40, 0xB9, 0xA0, 0x3E, 0x56, 0x21, 0x48, 0xD6, 0xDF, 0xEB };
+// -------------------------------------------------------------------------------------------------
+
+/**
+* helper routine to get past the version and flags field...
+*/
+static void
+digestISOFullBoxExtension(LFA_FileRef file, std::string isoPath, XMP_Int64& remainingSize, XMP_Uns8& version, XMP_Uns32& flags)
+{
+ version = LFA_ReadUns8(file);
+ flags = 0;
+
+ LFA_Read(file, &flags, 3, true); // read only 3 byte!
+ flags = flags >> 8; // (move to bit 0-23)
+ remainingSize -= 4;
+
+ tree->setKeyValue(isoPath + "version", fromArgs("%d", version));
+ tree->setKeyValue(isoPath + "flags", fromArgs("0x%.8X", flags));
+}
+
+static void
+digestInternationalTextSequence(LFA_FileRef file, std::string isoPath, XMP_Int64* remainingSize)
+{
+ XMP_Int64 miniBoxStringSize = tree->digest16u(file, isoPath + "size", true, true);
+ tree->digest16u(file, isoPath + "language code", true, true);
+ (*remainingSize) -= 4;
+ if ((*remainingSize) != miniBoxStringSize)
+ tree->addComment("WARNING: boxSize and miniBoxSize differ!");
+ tree->digestString(file, isoPath + "value", miniBoxStringSize, false);
+}
+
+/**
+* dumps one *or several* (ohter while loop) ISO Boxes within the indicated space:
+*
+* maxBoxLen is :== fileLen on top-level, otherwise available length of outer box (exluding header size naturally)
+*
+* (NB: reading (and displaying) box types, compat brands and other 4-letter stuff
+* as LE is somehow easier (might need adjustment for PPC though)
+*
+* practices:
+* compensate endianess using MakeUns32BE() prior to use as string, NOT for numeric compare
+*/
+static void
+DumpISOBoxes(LFA_FileRef file, XMP_Uns32 maxBoxLen, std::string _isoPath)
+{
+ XMP_Int64 endOfThisLevel = LFA_Tell(file) + maxBoxLen;
+ std::string origIsoPath(_isoPath);
+ std::string isoPath(_isoPath);
+ std::list<int> keys;
+ while (LFA_Tell(file) < endOfThisLevel)
+ {
+ XMP_Int64 boxHeaderSize = 8;
+
+ //assertMsg("No space for ISO box header", boxHeaderSize <= maxBoxLen );
+
+ //// certainly not enough room for another box?
+ // could be a 32bit zero trailing a udta
+ // or, uhm, something garbage-ish...
+ if (LFA_Tell(file) + boxHeaderSize > endOfThisLevel)
+ {
+ XMP_Int64 numUnusedBytes = (endOfThisLevel - LFA_Tell(file));
+ tree->digestString(file, isoPath + "unused", numUnusedBytes, false);
+ tree->addComment("'free' since too small for a box");
+
+ bool ok;
+ LFA_Seek(file, endOfThisLevel, SEEK_SET, &ok);
+ assertMsg("skippind to-small space failed (truncated file?)", ok);
+ continue; // could just as well: return
+ }
+
+ XMP_Int64 boxPos = LFA_Tell(file); // store here, output below
+ XMP_Int64 boxSize = tree->digest32u(file, "", true); // NB: 32bit <- 64bit
+ XMP_Uns32 boxType = tree->digest32u(file, "", false);
+
+ switch (boxSize)
+ {
+ case 0:
+ // A value of zero says that the box extends to the end of the file.
+ boxSize = (maxBoxLen - boxPos); // *** could be errorneous below top-level
+ break;
+ case 1:
+ // A value of 1 says that a 64-bit big endian size is written after the box type field, the data follows.
+ boxSize = LFA_ReadUns64_BE(file);
+ boxHeaderSize += 8;
+ break;
+ default:
+ break;
+ }
+
+ XMP_Uns32 tempBoxType = GetUns32LE(&boxType);
+ std::string boxString(fromArgs("%.4s", &tempBoxType));
+
+ if (boxString.size() != 0)
+ {
+ // substitute mac-copyright signs with an easier-to-handle "(c)"
+#if WIN_UNIVERSAL_ENV
+ if (boxString.at(0) == 0xffffffffffffffa9)
+#elif !IOS_ENV
+ if (boxString.at(0) == 0xa9)
+#else
+ if (boxString.at(0) == 0xffffffa9)
+#endif
+ boxString = std::string("(c)") + boxString.substr(1);
+ }
+ else
+ break;
+
+ isoPath = origIsoPath + boxString + "/";
+
+ // TEMP
+ // Log::info("pushing %s, endOfThisLevel: 0x%X", isoPath.c_str(), endOfThisLevel );
+ // printf ("%s \n", isoPath.c_str());
+ tree->pushNode(isoPath);
+#if ANDROID
+ tree->addComment("offset 0x%llX, size 0x%llX", boxPos, boxSize);
+#else
+ tree->addComment("offset 0x%I64X, size 0x%I64X", boxPos, boxSize);
+#endif
+
+
+ // endOfBoxPos saves the hassle of keeping the remainingSize up-to-date
+ // (which is only needed and only done, if usefull for the specific box)
+ XMP_Int64 remainingSize = boxSize - boxHeaderSize;
+ XMP_Int64 endOfBoxPos = LFA_Tell(file) + remainingSize;
+
+ // ---------------------------------------------
+ // for FullBoxes:
+ XMP_Uns8 version = 255;
+ XMP_Uns32 flags = 0xFFFFFF;
+
+ switch (boxType)
+ {
+ // container boxes (FULL), that contain (relevant) boxes:
+ case 0x6174656D: // meta, FULLBOX
+ if (isoPath == "moov/udta/meta/")
+ {
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ }
+ else if (isoPath == "moov/meta/")
+ {
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ }
+ else if (isoPath == "meta/") // for HEIF
+ {
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ }
+ else
+ break; //no area of interest (and navigate around some malformed files)
+
+ // container boxes (all non-FULL), that contain (relevant) boxes:
+ case 0x666E6964: //dinf
+ if (isoPath == "meta/dinf/")
+ {
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ }
+ break;
+ case 0x66657264: // dref
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ tree->digest32u(file, isoPath + "entry_count", true, true);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ case 0x206C7275: // for 'url' field, for 'urn' support to be added later
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ if (remainingSize > 0)
+ {
+ tree->digestString(file, isoPath + "location", 0);
+ }
+ break;
+ case 0x6D746970: //pitm
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ tree->digest16u(file, isoPath + "item_ID", true, true);
+ break;
+ case 0x666E6969: //iinf
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ if (version == 0)
+ {
+ tree->digest16u(file, isoPath + "entry_count", true, true);
+ }
+ else
+ {
+ tree->digest32u(file, isoPath + "entry_count", true, true);
+ }
+
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ case 0x65666E69: //infe
+ XMP_Uns16 item_id;
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ if ((version == 0) || (version == 1))
+ {
+ item_id = tree->digest16u(file, isoPath + "item_id", true, false);
+ remainingSize -= 2;
+ tree->digest16u(file, isoPath + "item_protection_index", true, true);
+ remainingSize -= 2;
+ item_name = tree->digestString(file, isoPath + "item_name", 0);
+ if (0 == item_name.compare(0, 4, "Exif"))
+ {
+ exif_item_id = item_id;
+ }
+ if (0 == item_name.compare(0, 4, "mime"))
+ {
+ mime_item_id = item_id;
+ }
+ remainingSize -= (item_name.size() + 1);
+ if (remainingSize == 0)
+ {
+ //there are no fields to process further
+ }
+ else if (remainingSize > 0)
+ {
+ //check for other fields here. If control reaches here, it means we have content_type field also
+ content_type = tree->digestString(file, isoPath + "content_type", 0);
+ remainingSize -= (content_type.size() + 1);
+ if (remainingSize == 0)
+ {
+ //there are no fields to process further
+ }
+ else if (remainingSize > 0)
+ {
+ // we have content_encoding present also
+ content_encoding = tree->digestString(file, isoPath + "content_encoding", 0);
+ }
+ else
+ {
+ //cout << "Some probem occurred when parsing infe box (content_encoding).Please check." << endl;
+ }
+ break;
+
+ }
+ else
+ {
+ //cout << "Some probem occurred when parsing infe box.Please check." << endl;
+ }
+ }
+ if (version >= 2)
+ {
+ if (version == 2)
+ {
+ item_id = tree->digest16u(file, isoPath + "item_id", true, false);
+ remainingSize -= 2;
+ }
+ else if (version == 3)
+ {
+ item_id = tree->digest32u(file, isoPath + "item_id", true, false);
+ remainingSize -= 4;
+ }
+ tree->digest16u(file, isoPath + "item_protection_index", true, true);
+ remainingSize -= 2;
+ item_type=tree->digest32u(file, isoPath + "item_type", true, true);
+ remainingSize -= 4;
+ item_name = tree->digestString(file, isoPath + "item_name", 0);
+ remainingSize -= (item_name.size() + 1);
+
+ if (0x45786966 == item_type)
+ {
+ exif_item_id = item_id;
+ }
+ if (0x6D696D65 == item_type)
+ {
+ mime_item_id = item_id;
+ }
+ else if (0x75726900 == item_type)
+ {
+ string item_uri_type=tree->digestString(file, isoPath + "item_uri_type", 0);
+ remainingSize -= (item_uri_type.size() + 1);
+ }
+
+
+ if (remainingSize == 0)
+ {
+ //there are no fields to process further
+ }
+ else if (remainingSize > 0)
+ {
+ //check for other fields here. If control reaches here, it means we have content_type field also
+ content_type = tree->digestString(file, isoPath + "content_type", 0);
+ remainingSize -= (content_type.size() + 1);
+ if (remainingSize == 0)
+ {
+ //there are no fields to process further
+ }
+ else if (remainingSize > 0)
+ {
+ // we have content_encoding present also
+ content_encoding = tree->digestString(file, isoPath + "content_encoding", 0);
+ }
+ else
+ {
+ cout << "Some probem occurred when parsing infe box (content_encoding).Please check." << endl;
+ }
+ }
+ }
+
+ //Get the data for exif - using value from exif_item_id
+ // Dump the exif metadata here
+ {
+ if (exif_item_id != 0 && tree->hasNode("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_offset"))
+ {
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ XMP_Uns32 offset = stoi(tree->getValue("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_offset")); // Here tiff stream starts after 16476 ( 16388 + 84 + 4 ) bytes from starting of PANA atom's content
+ //XMP_Uns8 *ExifSize = (XMP_Uns8*)malloc(4);
+ //Now point file to offset value
+ LFA_Seek(file, offset, SEEK_SET, &ok);
+ XMP_Uns32 blocksize = LFA_ReadUns32_BE(file);
+ LFA_Seek(file, offset + 4 + blocksize, SEEK_SET, &ok);
+ XMP_Uns64 tiffLength1 = (stoi(tree->getValue("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_length")));
+ XMP_Uns64 tiffLength = (stoi(tree->getValue("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_length")) - blocksize - 4);
+ XMP_Uns8 *tiffContent = (XMP_Uns8*)malloc(tiffLength);
+ LFA_Read(file, tiffContent, tiffLength, true);
+ DumpTIFF(tiffContent, tiffLength, offset, "HEIF Exif", "HEIF:Exif");
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ exif_item_id = NULL;
+ }
+ }
+ //Get the data for xmp - using value from mime_item_id
+ // Dump the xmp metadata here
+ {
+ if (mime_item_id != 0 && tree->hasNode("meta/iloc/item[" + to_string(mime_item_id) + "]/extent_offset"))
+ {
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ XMP_Uns32 offset = stoi(tree->getValue("meta/iloc/item[" + to_string(mime_item_id) + "]/extent_offset"));
+ XMP_Uns64 xmpLength = stoi(tree->getValue("meta/iloc/item[" + to_string(mime_item_id) + "]/extent_length"));
+ LFA_Seek(file, offset, SEEK_SET, &ok);
+ XMP_Uns8 *xmpContent = (XMP_Uns8*)malloc(xmpLength);
+ LFA_Read(file, xmpContent, xmpLength, true);
+ DumpXMP(xmpContent, xmpLength, offset, "XMP");
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ mime_item_id = NULL;
+ }
+ }
+ break;
+ case 0x636F6C69: //iloc
+ XMP_Uns16 deriveValues, item_count, extent_count;
+ XMP_Uns8 offset_size, length_size, base_offset_size, index_size;
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ // Read the contents here, do not add them to tree, move the seek to 16bits forward
+ deriveValues = LFA_ReadInt16_BE(file);
+ //add these values in the tree
+ offset_size = (deriveValues & 0xF000) >> 12;
+ length_size = (deriveValues & 0x0F00) >> 8;
+ base_offset_size = (deriveValues & 0x00F0) >> 4;
+ tree->setKeyValue(isoPath + "offset_size", fromArgs("%d", offset_size));
+ tree->setKeyValue(isoPath + "length_size", fromArgs("%d", length_size));
+ tree->setKeyValue(isoPath + "base_offset_size", fromArgs("%d", base_offset_size));
+ if (version == 1 || version == 2)
+ {
+ index_size = (deriveValues & 0x000F);
+ tree->setKeyValue(isoPath + "index_size", fromArgs("%d", index_size));
+ }
+ else
+ index_size = 0;
+
+
+ if (version < 2)
+ {
+ item_count = tree->digest16u(file, isoPath + "item_count", true, true);
+ }
+ else if (version == 2) {
+ item_count = tree->digest32u(file, isoPath + "item_count", true, true);
+ }
+ for (int i = 0; i < item_count; i++)
+ {
+ XMP_Uns32 item_id;
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ if (version < 2)
+ {
+ item_id = LFA_ReadUns16_BE(file);
+ }
+ else if (version == 2) {
+ item_id = LFA_ReadUns32_BE(file);
+ }
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+
+ string modifiedisoPath = isoPath + "item[" + to_string(item_id) + "]/";
+ if (version < 2)
+ {
+ item_id=tree->digest16u(file, modifiedisoPath + "item_ID", true, true);
+ }
+ else if (version == 2) {
+ item_id=tree->digest32u(file, modifiedisoPath + "item_ID", true, true);
+ }
+
+ if (version == 1 || version == 2)
+ {
+ //construction method
+ XMP_Uns16 deriveConstructionMethod = LFA_ReadInt16_BE(file);
+ XMP_Uns8 constructionMethod;
+ constructionMethod = (deriveConstructionMethod & 0xF000) >> 12;
+ tree->setKeyValue(isoPath + "construction_method", fromArgs("%d", constructionMethod));
+ }
+ tree->digest16u(file, modifiedisoPath + "data_reference_index", true, true);
+ if (base_offset_size != 0)
+ {
+ if (base_offset_size == 4) {
+ tree->digest32u(file, modifiedisoPath + "base_offset", true, true);
+ }
+ else if (base_offset_size == 8)
+ {
+ tree->digest64u(file, modifiedisoPath + "base_offset", true, true);
+ }
+ else
+ {
+ cout << "wrong value of base_offset received!! It should be either 0 or 4 or 8" << endl;
+ }
+ }
+
+ extent_count = tree->digest16u(file, modifiedisoPath + "extent_count", true, false);
+ for (int j = 0; j < extent_count; j++)
+ {
+ if ((version == 1 || version == 2) && (index_size > 0))
+ {
+
+ if (index_size == 4) {
+ tree->digest32u(file, modifiedisoPath + "index_size", true, false);
+ }
+ else if (index_size == 8)
+ {
+ tree->digest64u(file, modifiedisoPath + "index_size", true, false);
+ }
+ else
+ {
+ cout << "wrong value of index_size received!! It should be either 0 or 4 or 8" << endl;
+ }
+ }
+
+ if (offset_size > 0)
+ {
+ if (offset_size == 4) {
+ tree->digest32u(file, modifiedisoPath + "extent_offset", true, false);
+ }
+ else if (offset_size == 8)
+ {
+ tree->digest64u(file, modifiedisoPath + "extent_offset", true, false);
+ }
+ else
+ {
+ cout << "wrong value of offset_size received!! It should be either 0 or 4 or 8" << endl;
+ }
+ }
+
+ if (length_size > 0)
+ {
+ if (length_size == 4) {
+ tree->digest32u(file, modifiedisoPath + "extent_length", true, false);
+ }
+ else if (length_size == 8)
+ {
+ tree->digest64u(file, modifiedisoPath + "extent_length", true, false);
+ }
+ else
+ {
+ cout << "wrong value of length_size received!! It should be either 0 or 4 or 8" << endl;
+ }
+ }
+
+ }
+ }
+ //Get the data for exif - using value from exif_item_id
+ // Dump the exif metadata here
+ {
+ if (exif_item_id != 0 && tree->hasNode("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_offset"))
+ {
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ XMP_Uns32 offset = stoi(tree->getValue("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_offset")); // Here tiff stream starts after 16476 ( 16388 + 84 + 4 ) bytes from starting of PANA atom's content
+ //XMP_Uns8 *ExifSize = (XMP_Uns8*)malloc(4);
+ //Now point file to offset value
+ LFA_Seek(file, offset, SEEK_SET, &ok);
+ XMP_Uns32 blocksize = LFA_ReadUns32_BE(file);
+ LFA_Seek(file, offset + 4 + blocksize, SEEK_SET, &ok);
+ XMP_Uns64 tiffLength1 = (stoi(tree->getValue("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_length")));
+ XMP_Uns64 tiffLength = (stoi(tree->getValue("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_length")) - blocksize - 4);
+ XMP_Uns8 *tiffContent = (XMP_Uns8*)malloc(tiffLength);
+ LFA_Read(file, tiffContent, tiffLength, true);
+ DumpTIFF(tiffContent, tiffLength, offset, "HEIF Exif", "HEIF:Exif");
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ exif_item_id = NULL;
+ }
+ }
+ //Get the data for xmp - using value from mime_item_id
+ // Dump the xmp metadata here
+ {
+ if (mime_item_id != 0 && tree->hasNode("meta/iloc/item[" + to_string(exif_item_id) + "]/extent_offset"))
+ {
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ XMP_Uns32 offset = stoi(tree->getValue("meta/iloc/item[" + to_string(mime_item_id) + "]/extent_offset"));
+ XMP_Uns64 xmpLength = stoi(tree->getValue("meta/iloc/item[" + to_string(mime_item_id) + "]/extent_length"));
+ LFA_Seek(file, offset, SEEK_SET, &ok);
+ XMP_Uns8 *xmpContent = (XMP_Uns8*)malloc(xmpLength);
+ LFA_Read(file, xmpContent, xmpLength, true);
+ DumpXMP(xmpContent, xmpLength, offset, "XMP");
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ mime_item_id = NULL;
+ }
+ }
+
+ break;
+ case 0x66657269: //iref
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ case 0x676D6964: //dimg
+ XMP_Uns16 refCount;
+ tree->digest16u(file, isoPath + "from_item_ID", true, true);
+ refCount = tree->digest16u(file, isoPath + "reference_count", true, true);
+ for (int i = 0; i < refCount; i++)
+ {
+ tree->digest16u(file, isoPath + "to_item_ID", true, true);
+ }
+ break;
+ case 0x626D6874: //thmb
+ XMP_Uns16 refCount_thmb;
+ tree->digest16u(file, isoPath + "from_item_ID", true, true);
+ refCount_thmb = tree->digest16u(file, isoPath + "reference_count", true, true);
+ for (int i = 0; i < refCount_thmb; i++)
+ {
+ tree->digest16u(file, isoPath + "to_item_ID", true, true);
+ }
+ break;
+ case 0x63736463: //cdsc
+ XMP_Uns16 refCount_cdsc;
+ tree->digest16u(file, isoPath + "from_item_ID", true, true);
+ refCount_cdsc = tree->digest16u(file, isoPath + "reference_count", true, true);
+ for (int i = 0; i < refCount_cdsc; i++)
+ {
+ tree->digest16u(file, isoPath + "to_item_ID", true, true);
+ }
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ case 0x70727069: //iprp
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ /*case 0x6F637069: //ipco
+ DumpISOBoxes(file, remainingSize, isoPath);
+ break;
+ case 0x726c6f63: //colr
+ XMP_Uns32 colour_type;
+ colour_type=tree->digest32u(file, isoPath + "colour_type", true, true);
+ if (colour_type == 'nclx')
+ {
+ tree->digest16u(file, isoPath + "colour_primaries", true, true);
+ tree->digest16u(file, isoPath + "transfer_characteristics", true, true);
+ tree->digest16u(file, isoPath + "matrix_coefficients", true, true);
+ XMP_Uns8 full_range_flag = LFA_ReadUns8(file);
+ full_range_flag = ((full_range_flag & 0xF) >> 7);
+ tree->setKeyValue(isoPath + "full_range_flag", fromArgs("%d", full_range_flag));
+ }
+ else if (colour_type == 'rICC')
+ {
+
+ }
+ else if (colour_type == 'prof')
+ {
+ //unrestricted icc profile handling
+
+ }
+
+ break;*/
+ case 0x666E696D: // minf - "simple container, no direct content"
+ if (boxString == "minf" && isoPath != "moov/trak/mdia/minf/")
+ break;
+ case 0x6C627473: // stbl is a simple container, no direct content
+ {
+ TimeCodeTrack = false; // assume until we known better by a relevant
+ // moov/trak/mdia/minf/stbl/stsd/ of format tmcd
+ if (boxString == "stbl" && isoPath != "moov/trak/mdia/minf/stbl/")
+ break;
+ }
+ case 0x766F6F6D: // moov
+ case 0x6169646D: // mdia
+ case 0x61746475: // udta - user data
+ case 0x6B617274: // trak - track
+ case 0x74736C69: // ilst (contains cprt box)
+ {
+ // store and restore current position to not depend
+ // on sub-level mischief...
+ if (isoPath == "moov/meta/ilst/")
+ {
+ while (remainingSize > 0)
+ {
+ XMP_Uns32 metaItemSize = tree->digest32u(file, "", true);
+ XMP_Uns32 keyIndex = tree->digest32u(file, "", true);
+ string key_name = ISOMetaKeys[keyIndex - 1];
+ string temp_Path = isoPath + key_name + "/";
+ metaItemSize -= 4 + 4;
+ DumpISOBoxes(file, metaItemSize, temp_Path);
+ remainingSize -= (metaItemSize + 8);
+ }
+ }
+ else
+ {
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ }
+ }
+ break;
+
+ // known boxes, that need content extraction
+ case 0x70797466: // ftyp - file type
+ {
+ XMP_Uns32 majorBrand = LFA_ReadUns32_LE(file);
+ XMP_Uns32 minorVersion = LFA_ReadUns32_LE(file);
+
+ //data has been read in LE make it in BE
+ majorBrand = GetUns32LE(&majorBrand);
+ minorVersion = GetUns32LE(&minorVersion);
+
+ //Log::info( fromArgs( "major Brand: '%.4s' (0x%.8X)" , &majorBrand, MakeUns32BE(majorBrand) ));
+ //Log::info( fromArgs( "minor Version: 0x%.8X" , MakeUns32BE(minorVersion) ) );
+ tree->setKeyValue(isoPath + "majorBrand",
+ fromArgs("%.4s", &majorBrand),
+ fromArgs("0x%.8X", MakeUns32BE(majorBrand)));
+ tree->setKeyValue(isoPath + "minorVersion",
+ fromArgs("0x%.8X", MakeUns32BE(minorVersion)));
+
+ remainingSize -= 4 + 4;
+ //Log::info( fromArgs( "remaining Size: %d" , remainingSize ) );
+
+ while (remainingSize >= 4)
+ {
+ LFA_ReadUns32_LE(file);
+ // TODO: Concatenate for KeyValue...
+ //XMP_Uns32 compatVersion = LFA_ReadUns32_LE( file );
+ //Log::info( fromArgs( "compatible brand: '%.4s' (0x%.8X)" , &compatVersion, MakeUns32BE(compatVersion) ));
+ remainingSize -= 4;
+ }
+
+ // odd bytes left?
+ if (remainingSize > 0)
+ tree->addComment("WARNING: %d bytes left, considering FREE", remainingSize);
+
+ }
+ break;
+ case 0x61746164: // data (within itunes Metadata)
+ // all data atoms start with two common fields: a type indicator, and a locale indicator.
+ // each of these fields is four bytes long:
+ tree->digest32u(file, isoPath + "type", true, true);
+ tree->digest32u(file, isoPath + "locale", true, true);
+ remainingSize -= 8;
+ // rest is actual contents:
+ tree->digestString(file, isoPath + "value", remainingSize, false);
+ break;
+ case 0x64697575: // uuid
+ XMP_Uns8 uid[16];
+ tree->digest(file, isoPath + "uuidValue", uid, 16);
+ if (!strncmp((const char*)kUUID_XMP, (const char*)uid, 16))
+ tree->addComment(" - the XMP UUID !");
+
+ break;
+
+ case 0x65657266: // free
+ tree->addComment("free space");
+ break;
+
+ // FULL BOXES (w/o container boxes, above) **********************************
+ case 0x6468766D: // mvhd, FULLBOX, movie-header-box
+ {
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ if (version == 1)
+ {
+ tree->digest64u(file, isoPath + "creation_time", true, true);
+ tree->digest64u(file, isoPath + "modification_time", true, true);
+ tree->digest32u(file, isoPath + "timescale", true, true);
+ tree->digest64u(file, isoPath + "duration", true, true);
+ }
+ else if (version == 0)
+ {
+ tree->digest32u(file, isoPath + "creation_time", true, true);
+ tree->digest32u(file, isoPath + "modification_time", true, true);
+ tree->digest32u(file, isoPath + "timescale", true, true);
+ tree->digest32u(file, isoPath + "duration", true, true);
+ }
+ else
+ {
+ tree->addComment("WARNING: unknown mvhd version!");
+ }
+ // COULDDO more fields, but not needed right now.
+ }
+ break;
+ case 0x726C6468: // hdlr - handler reference
+ {
+ if (isoPath == "moov/trak/mdia/hdlr/")
+ {
+ if (remainingSize < 4 * 4)
+ break; // box too small...
+
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+
+ tree->digestString(file, isoPath + "quickTimeType", 4, false); // expecting: 'mhlr' - media handler
+ tree->digestString(file, isoPath + "subType", 4, false); // expecting: 'tmcd' - timecode
+ tree->digestString(file, isoPath + "manufacturer", 4, false); // e.g. 'appl'
+ break;
+ }
+ else if (isoPath == "moov/meta/hdlr/")
+ {
+ assertMsg("hdlr: version and flags must be zero", 0 == tree->digest32u(file, "", true));
+ LFA_Seek(file, 4, SEEK_CUR);
+
+ XMP_Uns32 hndlrTyp = LFA_ReadUns32_BE(file);
+ if (hndlrTyp == 0x6D647461)
+ tree->addComment("Handler Type = mdta");
+ else
+ break;
+
+ remainingSize -= 12;
+ XMP_Uns32 pos = LFA_Tell(file);
+ LFA_Seek(file, pos + remainingSize, SEEK_SET);
+ break;
+ }
+ else if (isoPath == "meta/hdlr/")
+ {
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ LFA_Seek(file, 4, SEEK_CUR);
+ tree->digestString(file, isoPath + "handler_type", 4, false);
+ break;
+ }
+ // rest doesn't bother us...
+ }
+ case 0x64737473: // stsd - timecode sample description table
+ {
+ if (isoPath != "moov/trak/mdia/minf/stbl/stsd/")
+ break;
+
+ // version (1 byte), flags (3 byte) - must be 0
+ assertMsg("stbl: version and flags must be zero", 0 == tree->digest32u(file, "", true));
+ // entryCount - must be 0
+ assertMsg("stbl: at least one entry needed", 1 <= tree->digest32u(file, "", true));
+ remainingSize -= 8;
+
+ // only dump first occurence
+ // ensure there's enough bytes for at least on stbl:
+ if (remainingSize < 29)
+ break; // MPEG4/al_sbr_twi_22_1_fsaac22.mv4 has a box that is smaller than this...
+ // hence can only break, not throw
+ XMP_Int64 entrySize = (XMP_Int64)tree->digest32u(file, "", true, true);
+ std::string format = tree->digestString(file, "", 4, false); // must be 'tmcd'
+ tree->pushNode(isoPath + format + "/");
+ if (format != "tmcd")
+ {
+ tree->addComment("irrelevant node");
+ tree->popNode();
+ break; // different party...
+ }
+
+ TimeCodeTrack = true; // we're in the right track
+
+ Skip(file, 6); // [6] reserved bytes
+ tree->digest16u(file, isoPath + "dataReferenceIndex", true, true); // (ignored)
+ tree->addOffset(file);
+ Skip(file, 4); // uint32 reserved
+ tree->digest32u(file, isoPath + "flags", true, true);
+ tree->digest32u(file, isoPath + "timeScale", true, true);
+ tree->digest32u(file, isoPath + "frameDuration", true, true);
+ Skip(file, 2); // skip ignored frame count, reserved
+
+ // ////////////////////////////// dig out 'trailing boxes'
+ // comparing "atom remains" vs. "entry" (probably must be '>='
+
+ // deduct the already digested...
+ entrySize -= 34; // ( 4+4+6+2+4+4+4+4+1+1 )
+ remainingSize -= 34; // (the atom-level value)
+
+ assertMsg("entry Size must be 0 or positive", entrySize >= 0);
+ assertMsg("must not overreach atom", entrySize <= remainingSize);
+
+ XMP_Int64 endOfTrailingBoxes = LFA_Tell(file) + remainingSize;
+ while (LFA_Tell(file) < endOfTrailingBoxes)
+ {
+ LFA_Tell(file);
+ DumpISOBoxes(file, entrySize, isoPath);
+ LFA_Tell(file);
+ }
+
+ assertMsg("did not boil down to zero", LFA_Tell(file) == endOfTrailingBoxes);
+
+ tree->popNode();
+ break;
+ }
+
+ case 0x63737473: // stsc - timecode sample description table
+ {
+ if (isoPath != "moov/trak/mdia/minf/stbl/stsc/")
+ break;
+ if (!TimeCodeTrack)
+ {
+ tree->addComment("not tcmd -> not of interest");
+ break; // not of interest
+ }
+
+ // version (1 byte), flags (3 byte) - must be 0
+ assertMsg("stbl: version and flags must be zero", 0 == tree->digest32u(file, "", true));
+ // entryCount - must be 0
+ assertMsg("stbl: at least one entry needed", 1 <= tree->digest32u(file, "", true));
+ remainingSize -= 8;
+
+ LFA_Tell(file);
+
+ tree->digest32u(file, isoPath + "firstChunkNo", true, true);
+ tree->digest32u(file, isoPath + "numSamplesPerChunk", true, true);
+ tree->digest32u(file, isoPath + "sampleDescriptionID", true, true);
+
+ break;
+ }
+
+ case 0x6F637473: // stco - timecode sample description table
+ {
+ if (isoPath != "moov/trak/mdia/minf/stbl/stco/")
+ break;
+ if (!TimeCodeTrack)
+ break; // not of interest
+
+ // version (1 byte), flags (3 byte) - must be 0
+ assertMsg("stbl: version and flags must be zero", 0 == tree->digest32u(file, "", true));
+ // entryCount - must be 0
+ assertMsg("stbl: at least one entry needed", 1 <= tree->digest32u(file, "", true));
+ remainingSize -= 8;
+
+ XMP_Int64 absOffset = tree->digest32u(file, isoPath + "absFileOffset32", true, true);
+
+ // recklessly navigate to that timecode media sample, grab value, return to old position...
+ XMP_Int64 oldPos = LFA_Tell(file);
+ LFA_Seek(file, absOffset, SEEK_SET, 0);
+ tree->digest32u(file, isoPath + "timecodeMediaSample", true, true);
+ LFA_Seek(file, oldPos, SEEK_SET, 0);
+
+ }
+ case 0x34366F63: // co64 - timecode sample description table -> 64 bit offset
+ {
+ if (isoPath != "moov/trak/mdia/minf/stbl/co64/")
+ break;
+
+ tree->digest64u(file, isoPath + "absFileOffset64", true, true);
+ break;
+ }
+
+ case 0x74727063: // cprt, FULLBOX
+ if (isoPath == "moov/udta/cprt/" || isoPath == "moov/uuid/udta/cprt/")
+ {
+
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+
+ // 1/8 byte ISO language padding = 1-bit value set to 0
+ // 1 7/8 bytes content language = 3 * 5-bits ISO 639-2 language code less 0x60
+ // - example code for english = 0x15C7
+ tree->digest16u(file, isoPath + "content language", true, true);
+ tree->addComment("(0x15C7 == english)");
+ // zero-terminated, actual string:
+ tree->digestString(file, isoPath + "value", 0);
+ }
+ else
+ {
+ // ISO - copyright (?)
+ // a container box, hunt for 'data' atom by recursion:
+ bool ok;
+ XMP_Int64 keep = LFA_Tell(file);
+ DumpISOBoxes(file, remainingSize, isoPath);
+ LFA_Seek(file, keep, SEEK_SET, &ok);
+ assertMsg("seek failed", ok);
+ }
+ break;
+ case 0x64686B74: // tkhd, FULLBOX
+ case 0x6E61656D: // mean, FULLBOX
+ case 0x656D616E: // name, FULLBOX
+ {
+ if (isoPath == "moov/trak/mdia/minf/stbl/stsd/name/") // this regrettably is a diffrent animal (international text sequence)
+ digestInternationalTextSequence(file, isoPath, &remainingSize);
+ else
+ digestISOFullBoxExtension(file, isoPath, remainingSize, version, flags);
+ }
+ break;
+ case 0x7379656B: //moov/meta/keys
+ {
+ LFA_Seek(file, 4, SEEK_CUR);
+ XMP_Uns32 keysCount = tree->digest32u(file, "", true);
+ tree->addComment("Number of Keys = %d", keysCount);
+ XMP_Uns32 index;
+ for (index = 1; index <= keysCount; index++)
+ {
+ XMP_Uns32 keySize = tree->digest32u(file, "", true);
+ keySize -= 4 + 4; // 4 bytes of key_size and key_namespace
+ string key_namespace = tree->digestString(file, "", 4);
+ if ((strcmp(key_namespace.c_str(), "mdta") == 0) || (strcmp(key_namespace.c_str(), "udta") == 0))
+ {
+ string key_value = tree->digestString(file, "", keySize);
+ ISOMetaKeys.push_back(key_value);
+ tree->pushNode(isoPath + key_value);
+ tree->addComment("Key Namespace = %s", key_namespace.c_str());
+ tree->popNode();
+ }
+ else
+ LFA_Seek(file, keySize, SEEK_CUR);
+ }
+ }
+ break;
+ // (c)-style quicktime boxes and boxes of no interest:
+ default:
+ if ((boxType & 0xA9) == 0xA9) // (c)something
+ {
+ if (0 == isoPath.compare(0, 10, "moov/udta/"))
+ { // => Quicktime metadata "international text sequence" ( size, language code, value )
+ digestInternationalTextSequence(file, isoPath, &remainingSize);
+ }
+ else
+ {
+ tree->addComment("WARNING: unknown flavor of (c)*** boxes, neither QT nor iTunes");
+ }
+ break;
+ }
+ //boxes of no interest:
+ break;
+ }
+
+ bool ok;
+ LFA_Seek(file, endOfBoxPos, SEEK_SET, &ok);
+ assertMsg("End-of-Box Seek failed (truncated file?)", ok);
+
+ tree->popNode();
+
+ } // while
+
+} // DumpISOBoxes()
+
+ // attempt to combine dumping of mpeg-4 and quicktime (mov) into one routine...
+static void
+DumpISO(LFA_FileRef file, XMP_Uns32 fileLen)
+{
+ TimeCodeTrack = false;
+
+ // see specificition at https://zerowing.corp.adobe.com/display/XMP/Embedding+Spec+MPEG4
+ DumpISOBoxes(file, fileLen, "");
+ assertMsg("truncated file/last box reached beyond end?", LFA_Tell(file) == fileLen);
+}
+
+// =================================================================================================
+
+static size_t GetASFObjectInfo(LFA_FileRef file, XMP_Uns32 objOffset, ASF_ObjHeader* objHeader, size_t nesting)
+{
+ LFA_Seek(file, objOffset, SEEK_SET);
+ LFA_Read(file, objHeader, 24, true);
+
+ objHeader->size = GetUns64LE(&objHeader->size);
+ XMP_Uns32 size32 = (XMP_Uns32)objHeader->size;
+
+ if (objHeader->size > 0xFFFFFFFF) {
+ tree->addComment("** ASF Object at offset 0x%X is over 4GB: 0x%.8X 0x%.8X",
+ objOffset, High32(objHeader->size), Low32(objHeader->size));
+ }
+
+ size_t infoIndex;
+ for (infoIndex = 0; kASF_KnownObjects[infoIndex].name != 0; ++infoIndex) {
+ if (memcmp(&objHeader->guid, &kASF_KnownObjects[infoIndex].guid, 16) == 0) break;
+ }
+
+ std::string indent(3 * nesting, ' ');
+
+ if (kASF_KnownObjects[infoIndex].name != 0) {
+ tree->addComment("%s %s Object, offset %u (0x%X), size %u",
+ indent.c_str(), kASF_KnownObjects[infoIndex].name, objOffset, objOffset, size32);
+ }
+ else {
+ tree->addComment("%s <<unknown object>>, offset %u (0x%X), size %u",
+ indent.c_str(), objOffset, objOffset, size32);
+ ASF_GUID guid;
+ guid.part1 = GetUns32LE(&objHeader->guid.part1);
+ guid.part2 = GetUns16LE(&objHeader->guid.part2);
+ guid.part3 = GetUns16LE(&objHeader->guid.part3);
+ guid.part4 = GetUns16LE(&objHeader->guid.part4);
+ tree->addComment("GUID %.8X-%.4X-%.4X-%.4X-%.4X%.8X",
+ guid.part1, guid.part2, guid.part3, guid.part4,
+ *(XMP_Uns16*)(&guid.part5[0]), *(XMP_Uns32*)(&guid.part5[2]));
+ }
+
+ if (objOffset != 0) tree->addComment(""); // Don't print newline for the real header.
+
+ return infoIndex;
+
+} // GetASFObjectInfo
+
+ // =================================================================================================
+
+static void PrinfASF_UTF16(LFA_FileRef file, XMP_Uns16 byteCount, const char * label)
+{
+ size_t filePos = LFA_Tell(file);
+ //FNO: note: has sideeffect on sDataPtr
+ CaptureFileData(file, 0, byteCount);
+
+ tree->setKeyValue(
+ label,
+ convert16Bit(false, sDataPtr, false, byteCount),
+ fromArgs("offset %d (0x%X), size %d", filePos, filePos, byteCount)
+ );
+}
+
+static void DumpASFFileProperties(LFA_FileRef file, XMP_Uns32 objOffset, XMP_Uns32 objLen)
+{
+ ASF_FileProperties fileProps;
+
+ if (objLen < kASF_FilePropertiesSize) {
+ tree->comment("** File Properties Object is too short");
+ return;
+ }
+
+ LFA_Seek(file, objOffset, SEEK_SET);
+ LFA_Read(file, &fileProps, kASF_FilePropertiesSize, true);
+
+ fileProps.flags = GetUns32LE(&fileProps.flags); // Only care about flags and create date.
+ fileProps.creationDate = GetUns64LE(&fileProps.creationDate);
+
+ bool bcast = (bool)(fileProps.flags & 1);
+ tree->setKeyValue("ASF:broadcast",
+ (bcast ? "set" : "not set")
+ );
+
+ XMP_Int64 totalSecs = fileProps.creationDate / (10 * 1000 * 1000);
+ XMP_Int32 nanoSec = ((XMP_Int32)(fileProps.creationDate - (totalSecs * 10 * 1000 * 1000))) * 100;
+ XMP_Int32 days = (XMP_Int32)(totalSecs / 86400);
+ totalSecs -= ((XMP_Int64)days * 86400);
+ XMP_Int32 hour = (XMP_Int32)(totalSecs / 3600);
+ totalSecs -= ((XMP_Int64)hour * 3600);
+ XMP_Int32 minute = (XMP_Int32)(totalSecs / 60);
+ totalSecs -= ((XMP_Int64)minute * 60);
+ XMP_Int32 second = (XMP_Int32)totalSecs;
+ XMP_DateTime binDate;
+ memset(&binDate, 0, sizeof(binDate));
+
+ binDate.year = 1601;
+ binDate.month = 1;
+ binDate.day = 1;
+
+ binDate.day += days;
+ binDate.hour = hour;
+ binDate.minute = minute;
+ binDate.second = second;
+ binDate.nanoSecond = nanoSec;
+
+ SXMPUtils::ConvertToUTCTime(&binDate);
+ std::string strDate;
+ SXMPUtils::ConvertFromDate(binDate, &strDate);
+
+ tree->setKeyValue("ASF:creation date",
+ fromArgs("%s (0x%.8X-%.8X)",
+ strDate.c_str(), High32(fileProps.creationDate), Low32(fileProps.creationDate))
+ );
+} // DumpASFFileProperties
+
+ // =================================================================================================
+
+static void DumpASFContentDescription(LFA_FileRef file, XMP_Uns32 objOffset, XMP_Uns32 objLen)
+{
+ ASF_ContentDescription contentDesc; // ! The lengths are in bytes.
+
+ if (objLen < kASF_ContentDescriptionSize) {
+ tree->comment("** Content Description Object is too short");
+ return;
+ }
+
+ LFA_Seek(file, objOffset, SEEK_SET);
+ LFA_Read(file, &contentDesc, kASF_ContentDescriptionSize, true);
+
+ contentDesc.titleLen = GetUns16LE(&contentDesc.titleLen);
+ contentDesc.authorLen = GetUns16LE(&contentDesc.authorLen);
+ contentDesc.copyrightLen = GetUns16LE(&contentDesc.copyrightLen);
+ contentDesc.descriptionLen = GetUns16LE(&contentDesc.descriptionLen);
+ contentDesc.ratingLen = GetUns16LE(&contentDesc.ratingLen);
+
+ PrinfASF_UTF16(file, contentDesc.titleLen, "ASF:title");
+ PrinfASF_UTF16(file, contentDesc.authorLen, "ASF:author");
+ PrinfASF_UTF16(file, contentDesc.copyrightLen, "ASF:copyright");
+ PrinfASF_UTF16(file, contentDesc.descriptionLen, "ASF:description");
+ PrinfASF_UTF16(file, contentDesc.ratingLen, "ASF:rating");
+
+} // DumpASFContentDescription
+
+ // =================================================================================================
+
+static void DumpASFContentBranding(LFA_FileRef file, XMP_Uns32 objOffset, XMP_Uns32 objLen)
+{
+ XMP_Uns32 fieldSize;
+
+ if (objLen < (16 + 8 + 4 * 4)) {
+ tree->comment("** Content Branding Object is too short");
+ return;
+ }
+
+ XMP_Uns32 fieldOffset = objOffset + 16 + 8 + 4;
+ LFA_Seek(file, fieldOffset, SEEK_SET);
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ fieldOffset += fieldSize;
+ LFA_Seek(file, fieldSize, SEEK_CUR); // Skip the banner data.
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ fieldOffset += fieldSize;
+ tree->setKeyValue("ASF:banner URL",
+ "",
+ fromArgs("offset %d (0x%X), size %d", fieldOffset, fieldOffset, fieldSize)
+ );
+
+ if (fieldSize != 0) {
+ CaptureFileData(file, 0, fieldSize);
+ //NB: not yet tested..., not sure if stopOnNull needed, thus using false
+ tree->changeValue(convert8Bit(sDataPtr, false, fieldSize));
+ }
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ fieldOffset += fieldSize;
+ tree->setKeyValue("ASF:copyright URL",
+ "",
+ fromArgs("offset %d (0x%X), size %d", fieldOffset, fieldOffset, fieldSize)
+ );
+
+ if (fieldSize != 0) {
+ CaptureFileData(file, 0, fieldSize);
+ //NB: not yet tested..., not sure if stopOnNull needed, thus using false
+ tree->changeValue(convert8Bit(sDataPtr, false, fieldSize));
+ }
+
+} // DumpASFContentBranding
+
+ // =================================================================================================
+
+static void DumpASFContentEncryption(LFA_FileRef file, XMP_Uns32 objOffset, XMP_Uns32 objLen)
+{
+ XMP_Uns32 fieldSize;
+
+ if (objLen < (16 + 8 + 4 * 4)) {
+ tree->addComment("** Content Encryption Object is too short");
+ return;
+ }
+
+ LFA_Seek(file, (objOffset + 16 + 8), SEEK_SET);
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ LFA_Seek(file, fieldSize, SEEK_CUR); // Skip the secret data.
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ LFA_Seek(file, fieldSize, SEEK_CUR); // Skip the protection type.
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ CaptureFileData(file, 0, fieldSize);
+ PrintOnlyASCII_8(sDataPtr, fieldSize);
+
+ LFA_Read(file, &fieldSize, 4, true);
+ fieldSize = GetUns32LE(&fieldSize);
+ CaptureFileData(file, 0, fieldSize);
+ PrintOnlyASCII_8(sDataPtr, fieldSize);
+
+} // DumpASFContentEncryption
+
+ // =================================================================================================
+
+static void DumpASFHeaderExtension(LFA_FileRef file, XMP_Uns32 extOffset, XMP_Uns32 extLen)
+{
+ // The Header Extension Object is a child of the Header Object and the parent of nested objects.
+
+ XMP_Uns32 extEnd = extOffset + extLen;
+
+ XMP_Uns32 childLen;
+ XMP_Uns32 childOffset;
+
+ ASF_ObjHeader childHeader;
+
+ for (childOffset = (extOffset + kASF_HeaderExtensionSize); childOffset < extEnd; childOffset += childLen) {
+ (void)GetASFObjectInfo(file, childOffset, &childHeader, 2);
+ childLen = (XMP_Uns32)childHeader.size;
+ }
+
+ if (childOffset != extEnd) {
+ tree->addComment("** Invalid end to nested Header Extension objects, offset %u", childOffset);
+ }
+
+} // DumpASFHeaderExtension
+
+ // =================================================================================================
+
+static void
+DumpASF(LFA_FileRef file, XMP_Uns32 asfLen)
+{
+ // An ASF file contains objects of the form:
+ // A 16 byte GUID giving the object's type and use
+ // A little endian 64-bit object length, includes the GUID and length (== 24 + data size)
+ // The object's data
+ // Objects can be nested. The top level of a file is a Header Object, followed by a Data Object,
+ // followed by any number of "other" top level objects, followed by any number of index objects.
+ // There is legacy metadata in certain nested objects within the Header Object. The XMP is an
+ // "other" top level object.
+
+ size_t infoIndex;
+
+ XMP_Uns32 objLen;
+ XMP_Uns32 objOffset;
+ ASF_ObjHeader objHeader;
+
+ tree->comment("ASF file Object layout");
+
+ // Dump the Header Object's content, looking for legacy metadata.
+ infoIndex = GetASFObjectInfo(file, 0, &objHeader, 0);
+ XMP_Uns32 headerLen = (XMP_Uns32)objHeader.size;
+
+ if (kASF_KnownObjects[infoIndex].kind != kASFObj_Header) {
+ tree->comment("** First object is not the Header Object");
+ return;
+ }
+
+ XMP_Uns32 nestedCount;
+ LFA_Seek(file, 24, SEEK_SET);
+ LFA_Read(file, &nestedCount, 4, true);
+ nestedCount = GetUns32LE(&nestedCount);
+ tree->addComment("%u nested objects", nestedCount);
+
+ for (objOffset = (16 + 8 + 4 + 2); objOffset < headerLen; objOffset += objLen, --nestedCount) {
+ infoIndex = GetASFObjectInfo(file, objOffset, &objHeader, 1);
+ objLen = (XMP_Uns32)objHeader.size;
+
+ switch (kASF_KnownObjects[infoIndex].kind) {
+ case kASFObj_FileProperties:
+ DumpASFFileProperties(file, objOffset, objLen);
+ break;
+ case kASFObj_ContentDesc:
+ DumpASFContentDescription(file, objOffset, objLen);
+ break;
+ case kASFObj_ContentBrand:
+ DumpASFContentBranding(file, objOffset, objLen);
+ break;
+ case kASFObj_ContentEncrypt:
+ DumpASFContentEncryption(file, objOffset, objLen);
+ break;
+ case kASFObj_HeaderExtension:
+ DumpASFHeaderExtension(file, objOffset, objLen);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((objOffset != headerLen) || (nestedCount != 0)) {
+ tree->comment("** Invalid end to nested Header objects, offset %u, count %u",
+ objOffset, nestedCount);
+ objOffset = headerLen;
+ }
+
+ // Dump the basic info for the remaining objects, looking for the XMP along the way.
+ infoIndex = GetASFObjectInfo(file, objOffset, &objHeader, 0);
+ objLen = (XMP_Uns32)objHeader.size;
+
+ if (kASF_KnownObjects[infoIndex].kind != kASFObj_Data) {
+ tree->addComment("** Second object is not the Data Object");
+ if (kASF_KnownObjects[infoIndex].kind == kASFObj_XMP) {
+ if (sXMPPtr == 0)
+ CaptureXMPF(file, (objOffset + 24), (objLen - 24));
+ else
+ tree->addComment("** Multiple XMP objects");
+ }
+ }
+
+ for (objOffset += objLen; objOffset < asfLen; objOffset += objLen) {
+ GetASFObjectInfo(file, objOffset, &objHeader, 0);
+ objLen = (XMP_Uns32)objHeader.size;
+
+ if (kASF_KnownObjects[infoIndex].kind == kASFObj_XMP) {
+ if (sXMPPtr == 0)
+ CaptureXMPF(file, (objOffset + 24), (objLen - 24));
+ else
+ tree->addComment("** Multiple XMP objects");
+ }
+ }
+
+ if (objOffset != asfLen) tree->addComment("** Invalid end to top level objects: %u", objOffset);
+ if (sXMPPtr != 0) DumpXMP("ASF XMP object");
+
+} // DumpASF
+
+ // =================================================================================================
+
+static void
+DumpUCF(LFA_FileRef file, XMP_Int64 len)
+{
+ //////////////////////////////////////////////////////////////////////
+ // constants
+ const static XMP_Uns32 UCF_HS_CONTENTFILE = 0x04034b50;
+ const static XMP_Uns32 UCF_CD_FILE_HEADER = 0x02014b50; //central directory - file header
+ const static XMP_Uns32 UCF_ZIP64_END_OF_CD_RECORD = 0x06064b50;
+ const static XMP_Uns32 UCF_ZIP64_END_OF_CD_LOCATOR = 0x07064b50;
+ const static XMP_Uns32 UCF_CD_END = 0x06054b50;
+
+ const static XMP_Int32 UCF_COMMENT_MAX = 0xFFFF;
+ const static XMP_Int32 UCF_EOD_FIXED_SIZE = 22;
+
+ const static XMP_Int32 UCF_ZIP64_LOCATOR_FIXED_SIZE = 20;
+ const static XMP_Int32 UCF_ZIP64_RECORD_FIXED_SIZE = 56;
+
+ //////////////////////////////////////////////////////////////////////
+ // variables:
+ XMP_Int64 curPos = 0;
+ bool isZip64;
+
+ XMP_Uns32 numDirEntries;
+
+ XMP_Uns64 size_CD;
+ XMP_Uns64 offset_Zip64_EndOfCD_Record = 0;
+ XMP_Uns64 offset_CD;
+
+ typedef std::list<XMP_Int64> OffsetStack;
+ OffsetStack contentFiles;
+ contentFiles.clear(); //precaution for mac
+
+ //////////////////////////////////////////////////////////////////////
+ // prolog:
+ tree->comment("len is 0x%I64X", len);
+ tree->comment("inherently parsing bottom-up");
+ if (len > 0xFFFFFFFFl)
+ tree->comment("info: >4GB ==> most like zip64 !");
+
+ //////////////////////////////////////////////////////////////////////
+ // parse bottom up:
+
+ /////////////////////////////////////////////////////////////////////
+ // zip comment:
+ XMP_Int32 zipCommentLen = 0;
+ for (; zipCommentLen <= UCF_COMMENT_MAX; zipCommentLen++)
+ {
+ LFA_Seek(file, -zipCommentLen - 2, SEEK_END);
+ if (LFA_ReadUns16_LE(file) == zipCommentLen) //found it?
+ {
+ //double check, might just look like comment length (actually be 'evil' comment)
+ LFA_Seek(file, -UCF_EOD_FIXED_SIZE, SEEK_CUR);
+ if (LFA_ReadUns32_LE(file) == UCF_CD_END) break; //heureka, directory ID
+ // 'else': just go on
+ }
+ }
+ tree->comment(fromArgs("zip Comment length: %d", zipCommentLen));
+
+ //was it a break or just not found ?
+ assertMsg("zip broken near end or invalid comment", zipCommentLen < UCF_COMMENT_MAX);
+
+ /////////////////////////////////////////////////////////////////////
+ // End of CDR:
+ LFA_Seek(file, -UCF_EOD_FIXED_SIZE - zipCommentLen, SEEK_END);
+ curPos = LFA_Tell(file);
+ tree->pushNode("End of Central Directory");
+ tree->addOffset(file);
+ {
+ assertMsg("expected 'end of central directory record'", UCF_CD_END == tree->digest32u(file));
+ assertMsg("UCF allow single-volume zips only", 0 == tree->digest16u(file)); //volume number (0,1,..)
+ assertMsg("UCF allow single-volume zips only(thus directory must be in 0)", 0 == tree->digest16u(file, "")); //volume number (0,1,..)
+
+ numDirEntries = tree->digest16u(file, "number of directory entries");
+ tree->digest16u(numDirEntries, file, "number of total directory entries");
+
+ size_CD = tree->digest32u(file, "size of central directory", false, true);
+ offset_CD = tree->digest32u(file, "offset of central directory", false, true);
+ if (offset_CD == 0xFFFFFFFF) tree->addComment("apparently zip-64");
+
+ XMP_Uns16 zipCommentLengReverify = tree->digest16u(file, "zip comment length");
+ assertMsg("zipCommentLengReverify failed", zipCommentLengReverify == zipCommentLen);
+ }
+ tree->popNode();
+ /////////////////////////////////////////////////////////////////////
+ // Zip64 End Of CD Locator
+ LFA_Seek(file, curPos - UCF_ZIP64_LOCATOR_FIXED_SIZE, SEEK_SET);
+
+ //tree->comment("offset is %X", LFA_Tell(file) );
+ //tree->comment("peek is %X", Peek32u(file) );
+
+ if (Peek32u(file) != UCF_ZIP64_END_OF_CD_LOCATOR)
+ {
+ tree->comment("no Zip64 CDL -> no Zip64");
+ assertMsg("offset FFFF FFFF indicates zip-64, but no Zip64 CDL found", offset_CD != 0xFFFFFFFF);
+ isZip64 = false;
+ }
+ else
+ {
+ isZip64 = true;
+ tree->pushNode("Zip64 End-Of-CD Locator");
+ tree->addOffset(file);
+
+ tree->digest32u(file, "sig", false, true);
+ assertMsg("'numOfDisk with central start dir' must be 0",
+ 0 == tree->digest32u(file, "disk with start dir"));
+ tree->digest64u(&offset_Zip64_EndOfCD_Record, file, "Zip64 End Of CD Offset", false, true);
+
+ tree->digest32u( /* deactived while bug #1742179: 1,*/ file, "total num of disks", false, true);
+ tree->popNode();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ // Zip64 End of CD Record
+ if (isZip64)
+ {
+ XMP_Uns64 size_Zip64_EndOfCD_Record;
+ tree->pushNode("Zip64 End of CD Record");
+ LFA_Seek(file, offset_Zip64_EndOfCD_Record, SEEK_SET);
+ tree->addOffset(file);
+
+ tree->digest32u(UCF_ZIP64_END_OF_CD_RECORD, file, "sig", false, true);
+ tree->digest64u(&size_Zip64_EndOfCD_Record, file, "size of zip64 CDR", false, true);
+ tree->digest16u(file, "made by", false, true);
+ tree->digest16u(file, "needed to extract", false, true);
+ tree->digest32u((XMP_Uns32)0, file, "number of this disk", false, true);
+ tree->digest32u((XMP_Uns32)0, file, "disk that contains start of CD", false, true);
+ tree->digest64u((XMP_Uns64)numDirEntries, file, "total Num of Entries This Disk", false, false);
+ tree->digest64u((XMP_Uns64)numDirEntries, file, "total Num of Entries", false, false);
+ //TODO assert agtainst each other and above
+ tree->digest64u(&size_CD, file, "size_CD", false, true);
+ tree->digest64u(&offset_CD, file, "offset_CD", false, true);
+ XMP_Int64 lenExtensibleSector = UCF_ZIP64_RECORD_FIXED_SIZE - size_Zip64_EndOfCD_Record;
+ tree->comment("zip64 extensible data sector (%d bytes)", lenExtensibleSector);
+ //sanity test:
+ Skip(file, lenExtensibleSector);
+ assertMsg("numbers don't add up", Peek32u(file) != UCF_ZIP64_END_OF_CD_LOCATOR);
+
+ tree->popNode();
+ }
+
+ /////////////////////////////////////////////////////////////////////
+ // parse Central directory structure: content file 1..n
+ tree->pushNode("Central directory structure:");
+ LFA_Seek(file, offset_CD, SEEK_SET);
+ tree->addOffset(file);
+
+ for (XMP_Uns32 contentFileNo = 1; contentFileNo <= numDirEntries; contentFileNo++)
+ {
+ tree->pushNode("File Header No %d:", contentFileNo);
+ tree->addOffset(file);
+
+ XMP_Uns16 version, flags, cmethod;
+ XMP_Uns32 crc, compressed_size, uncompressed_size32;
+ XMP_Uns64 offsetLocalHeader = 0;
+ bool usesDescriptionHeader;
+
+ tree->digest32u(UCF_CD_FILE_HEADER, file, "sig", false, true);
+
+ tree->digest16u(file, "version made by", false, true);
+ tree->digest16u(&version, file, "version needed to extract");
+ assertMsg(fromArgs("illegal 'version needed to extract' (must be 10,20 or 45, was %u)", version),
+ (version == 10 || version == 20 || version == 45));
+
+ tree->digest16u(&flags, file, "general purpose bit flags", false, true);
+ assertMsg("no zip encryption must be used", (flags & 0x1) == 0);
+ usesDescriptionHeader = ((flags & 0x8) != 0);
+ if (usesDescriptionHeader) tree->addComment("uses description header");
+
+ tree->digest16u(&cmethod, file, "compression method");
+ assertMsg("illegal compression method (must be 0 or 8(flate))!", (cmethod == 0 || cmethod == 8));
+
+ tree->digest(file, "last mod file time", 0, 2);
+ tree->digest(file, "last mod file date", 0, 2);
+
+ tree->digest32u(&crc, file); //crc-32
+ tree->digest32u(&compressed_size, file, "compressed size");
+ tree->digest32u(&uncompressed_size32, file, "uncompressed size");
+
+ XMP_Uns16 size_filename, size_extra, size_comment;
+
+ tree->digest16u(&size_filename, file, "size filename");
+ assertMsg("unusual name length length (broken file?)", size_filename>0 && size_filename < 500); //discover parsing nonsense...
+ tree->digest16u(&size_extra, file, "size extra field");
+ tree->digest16u(&size_comment, file, "size file comment");
+ tree->digest16u((XMP_Uns16)0, file, "disk start no");
+
+ tree->digest16u(file, "internal attribs");
+ tree->digest32u(file, "external attribs");
+
+ offsetLocalHeader = tree->digest32u(file, "relative offset local header", false, true); // Int64 <== Uns32
+
+ // name of file, optional relative path, strictly forward slashes.
+ assert(size_filename != 0);
+ std::string filename = tree->digestString(file, "filename", size_filename); //NOT zero-terminated
+
+ if (contentFileNo == 1)
+ {
+ assert(size_extra == 0); //spec guarantes mimetype content at 38 <=> extraFieldLen == 0
+ assertMsg(
+ fromArgs("first file in UCF must be called mimetype, was %s", filename.c_str()),
+ (size_filename == 8) && (filename == "mimetype"));
+ }
+
+ if (size_extra != 0)
+ {
+ tree->pushNode("extraField");
+ XMP_Int32 remaining = size_extra;
+ while (remaining > 0)
+ {
+ assertMsg("need 4 bytes for next header ID+len", remaining >= 4);
+ XMP_Uns16 headerID = tree->digest16u(file, "headerID", false, true);
+ XMP_Uns16 dataSize = tree->digest16u(file, "data size", false, true);
+ remaining -= 4;
+ assertMsg("actual field lenght not given", remaining >= dataSize);
+ if (headerID == 0x1) //we only care about "Zip64 extended information extra field"
+ {
+ tree->digest64u(&offsetLocalHeader, file, "64bit offset", false, true);
+ remaining -= 8;
+ }
+ else
+ {
+ Skip(file, dataSize);
+ remaining -= dataSize;
+ }
+
+ }
+ tree->popNode();
+ }
+
+ //now that regular 32 bit and zip-64 is through...
+ if (contentFileNo == 1)
+ {
+ assertMsg("first header offset (aka content file offset) must (naturally) be 0", offsetLocalHeader == 0);
+ }
+ else
+ {
+ assertMsg("local header offset (aka content file offset) must not be 0", offsetLocalHeader != 0);
+ }
+ contentFiles.push_back(offsetLocalHeader);
+
+ if (size_comment != 0)
+ {
+ tree->digest(file, "file comment", 0, size_comment);
+ }
+ tree->popNode(); //file header
+ }
+ tree->popNode(); //central directory structure
+
+ /////////////////////////////////////////////////////////////////////
+ // Content Files (incl. Headers, etc)
+ for (XMP_Uns16 cfNo = 1; cfNo <= numDirEntries; cfNo++)
+ {
+ //vars
+ XMP_Uns32 compressed_size, uncompressed_size, crc32;
+ XMP_Uns16 version, nameLen, extraFieldLen;
+
+ tree->pushNode("Content File %d:", cfNo);
+
+ assert(!contentFiles.empty());
+ XMP_Int64 fileHeaderOffset = contentFiles.front();
+ contentFiles.pop_front();
+
+ bool ok;
+ LFA_Seek(file, fileHeaderOffset, SEEK_SET, &ok);
+ tree->addOffset(file);
+ assert(ok);
+
+ //local file header
+ tree->digest32u(UCF_HS_CONTENTFILE, file, "sig", false, true);
+ tree->digest16u(&version, file, "version");
+ assertMsg("illegal 'version needed to extract' (must be 10,20 or 45, was %u)",
+ (version == 10 || version == 20 || version == 45));
+ Skip(file, 8);
+ tree->digest32u(&crc32, file, "crc-32", false, true);
+ tree->digest32u(&compressed_size, file, "compressed", false, false);
+ tree->digest32u(&uncompressed_size, file, "uncompressed", false, false);
+ tree->digest16u(&nameLen, file, "file name length", false, false);
+ tree->digest16u(&extraFieldLen, file, "extra field length", false, false);
+
+ assert(nameLen != 0);
+ assertMsg("unusual name length length (broken file?)", nameLen>0 && nameLen < 500); //discover parsing nonsense...
+
+ // name of file, optional relative path, strictly forward slashes.
+ std::string filename = tree->digestString(file, "filename", nameLen); //NOT zero-terminated
+ if (cfNo == 1)
+ {
+ assertMsg("first file in UCF muste be called mimetype", filename == "mimetype");
+ assert(extraFieldLen == 0); //spec guarantes mimetype content at 38 <=> extraFieldLen == 0
+ assert(LFA_Tell(file) == 38);
+ tree->digestString(file, "file data (mimetype)", compressed_size);
+ }
+ else //not the first mimetype file thing
+ {
+ // FILE DATA =============================================================
+ if (extraFieldLen != 0) // may indeed not exist, and lenght=0 must not be passed into digestString()
+ tree->digestString(file, "extra field", extraFieldLen); //NOT zero-terminated
+ tree->setKeyValue("file data", "", fromArgs("skipping %u bytes", compressed_size));
+ Skip(file, compressed_size);
+ }
+ tree->popNode();
+ }
+
+ tree->pushNode("");
+ tree->popNode();
+} // DumpUCF
+
+ // =================================================================================================
+
+ // AVI and WAV files are RIFF based. Although they have usage differences, we can have a common
+ // dumper. This might need changes for other RIFF-based files, e.g. for specific legacy
+ // metadata. RIFF is a chunky format. AVI and WAV have an outermost RIFF chunk. The XMP is in a
+ // top level "_PMX" chunk. Legacy metadata for WAV is in a top level LIST/INFO chunk. Legacy
+ // metadata for AVI is in a variety of places, don't have specs at present. Free space can be
+ // JUNK or JUNQ.
+ //
+ // A RIFF chunk contains:
+ // - A 4 byte chunk ID, typically ASCII letters
+ // - A little endian UInt32 size of the chunk data
+ // - The chunk data
+ // - Pad byte if the chunk data length is odd (added on 2007-03-22)
+
+ // The ID is written in "reading order", e.g. the 'R' in "RIFF" is first in the file. Chunks
+ // must start on even offsets. A pad byte of 0 is written after the data if necessary. The size
+ // does not include the pad, nor the ID and size fields. Some chunks contain nested chunks,
+ // notably the RIFF and LIST chunks do. These have the layout:
+ // - A 4 byte chunk ID, typically ASCII letters
+ // - A little endian UInt32 size of the chunk data
+ // - A 4 byte usage ID, typically ASCII letters
+ // - The nested chunks
+
+ // reads maxSize bytes from file (not "up to", exactly fullSize)
+ // puts it into a string, sets respective tree property
+static void setFixedBEXTField(LFA_FileRef file, std::string propName, XMP_Int64 fullSize)
+{
+ char* descriptionBuffer = new char[fullSize + 2];
+ LFA_Read(file, descriptionBuffer, fullSize, true);
+ descriptionBuffer[fullSize] = '\0'; // tack on, in case not contained
+ // parse till first \0
+ std::string description(descriptionBuffer);
+ tree->setKeyValue(propName, description);
+ delete[] descriptionBuffer;
+}
+
+struct ChunkSize64 // declare ChunkSize64 structure
+{
+ XMP_Uns32 chunkId; // chunk ID (i.e. "big1" - this chunk is a big one)
+ XMP_Uns64 chunkSize; //
+};
+
+struct DataSize64Chunk // declare DataSize64Chunk structure
+{
+ XMP_Uns32 chunkId; // ds64
+ XMP_Uns32 chunkSize; // 4 byte size of the ds64 chunk
+ XMP_Uns64 riffSize; // size of RF64 block
+ XMP_Uns64 dataSize; // size of data chunk
+ XMP_Uns64 sampleCount; // sample count of fact chunk
+ XMP_Uns32 tableLength; // number of valid entries in array "table"
+ std::vector< ChunkSize64 > table;
+};
+
+
+
+static XMP_Uns64 parseRF64(LFA_FileRef file, DataSize64Chunk* rf64Sizes)
+{
+ XMP_Int64 chunkPos = LFA_Tell(file);
+ rf64Sizes->chunkId = tree->digest32u(file, "", false);
+ std::string ds64ChunkID_ST(fromArgs("%.4s", &rf64Sizes->chunkId));
+ assertMsg("Not a valid RF64 file!", ds64ChunkID_ST == "ds64");
+
+ rf64Sizes->chunkSize = tree->digest32u(file, "", false);
+
+ XMP_Uns32 bitCnt = 0;
+ rf64Sizes->riffSize = tree->digest64u(file, "", false);
+ rf64Sizes->dataSize = tree->digest64u(file, "", false);
+ rf64Sizes->sampleCount = tree->digest64u(file, "", false);
+
+ rf64Sizes->tableLength = tree->digest32u(file, "", false);
+
+ bitCnt = 28;
+
+ for (XMP_Uns32 i = 0; i < rf64Sizes->tableLength; i++)
+ {
+ ChunkSize64 tmp;
+ tmp.chunkId = tree->digest32u(file, "", false);
+ tmp.chunkSize = tree->digest64u(file, "", false);
+ rf64Sizes->table.push_back(tmp);
+
+ bitCnt += 12;
+ }
+
+ // is there a rest to skip?
+ XMP_Uns32 rest = rf64Sizes->chunkSize - bitCnt;
+ if (rest != 0)
+ {
+ Skip(file, rest);
+ }
+ // return correct RIFF size
+ return rf64Sizes->riffSize;
+
+}
+
+static XMP_Uns64 getRealSize(bool isOutermost, std::string chunkID, LFA_FileRef file, DataSize64Chunk* sizeChunk)
+{
+ if (isOutermost)
+ {
+ return parseRF64(file, sizeChunk);
+ }
+ else
+ {
+ if (chunkID == "data")
+ {
+ return sizeChunk->dataSize;
+ }
+ else
+ {
+ // search table
+ for (XMP_Uns32 i = 0; i < sizeChunk->tableLength; i++)
+ {
+ std::string idString(fromArgs("%.4s", &sizeChunk->table[i].chunkId));
+ if (idString == chunkID)
+ {
+ return sizeChunk->table[i].chunkSize;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static void
+DumpRIFFChunk(LFA_FileRef file, XMP_Int64 parentEnd, std::string origChunkPath, bool bigEndian = false, DataSize64Chunk* rf64Sizes = NULL)
+{
+
+ while (LFA_Tell(file) < parentEnd)
+ {
+ bool isOutermost = origChunkPath.empty();
+
+ XMP_Int64 chunkPos = LFA_Tell(file);
+ XMP_Int64 fileSize = LFA_Measure(file);
+ XMP_Int64 fileTail = fileSize - chunkPos;
+
+ if (fileTail < 8)
+ {
+ tree->pushNode("** unknown bytes **");
+ tree->addOffset(file);
+ tree->addComment("size: 0x%llX", fileTail);
+ Skip(file, fileTail); // Already read the 8 byte header.
+ tree->popNode();
+ }
+ else
+ {
+ XMP_Uns32 tmp = tree->digest32u(file, "", true);
+ XMP_Uns32 chunkID = GetUns32BE(&tmp); // flip if necessary for LE systems
+
+ std::string idString(fromArgs("%.4s", &chunkID));
+
+ XMP_Int64 chunkSizeWOHeader = tree->digest32u(file, "", bigEndian);
+ XMP_Int64 validRF64Header = chunkSizeWOHeader;
+ XMP_Uns32 chunkType = 0;
+ std::string typeString = "";
+ // only RIFF and LIST contain subchunks...
+ bool hasSubChunks = (idString == "RIFF") || (idString == "RF64") || (idString == "FORM") || (idString == "LIST") || (idString == "APPL");
+
+ if (hasSubChunks)
+ {
+ XMP_Uns32 tmp = tree->digest32u(file, "", true);
+ chunkType = GetUns32BE(&tmp); // flip if necessary for LE systems
+ typeString = fromArgs("%.4s", &chunkType);
+ }
+ //get inner ID 'type' as in 'listType', 'fileType', ...
+ //XMP_Uns32 chunkType = tree->digest32u( file );
+
+ if (chunkSizeWOHeader == 0xFFFFFFFF) //RF64 size for children
+ {
+
+ chunkSizeWOHeader = getRealSize(isOutermost, idString, file, rf64Sizes);
+ }
+
+ XMP_Int64 chunkSize = chunkSizeWOHeader + 8;// NB: XMPInt64 <- XMPUns32
+ //adding size of id and length field itself
+
+ // calculate size if size field seems broken
+ if (chunkSize > parentEnd)
+ chunkSize = parentEnd - chunkPos;
+
+
+ std::string chunkPath = isOutermost ? (idString) : (origChunkPath + "/" + idString);
+
+
+ // check special case of trailing bytes not in a valid RIFF structure
+ if (isOutermost && idString != "RIFF"&& idString != "FORM" && idString != "RF64")
+ {
+ //dump undefined bytes till the end of the file
+ tree->pushNode("** unknown bytes **");
+ chunkSize = parentEnd - chunkPos; // get size through calculation (and not from size bytes)
+ tree->addComment("offset 0x%llX, size: 0x%llX", chunkPos, chunkSize);
+ Skip(file, chunkSize - 8); // Already read the 8 byte header.
+ tree->popNode();
+ }
+ else
+ {
+
+ bool skipper = false;
+ if (hasSubChunks)
+ {
+ if (isOutermost)
+ {
+ assertMsg("level-0 chunk must be AVI, AVIX, WAVE, AIFF, AIFC",
+ (typeString == "AVI ") || (typeString == "AVIX") || (typeString == "WAVE")
+ || (typeString == "AIFF") || (typeString == "AIFC"));
+ }
+
+ chunkPath = chunkPath + ":" + typeString;
+ XMP_Uns64 dsHeaderSize = 0xFFFFFFFF;
+ if (chunkSize != fileSize && idString == "RF64")
+ {
+ XMP_Uns32 tmp = tree->digest32u(file, "", true);
+ XMP_Uns32 chunkID = GetUns32BE(&tmp); // flip if necessary for LE systems
+
+ std::string dsString(fromArgs("%.4s", &chunkID));
+ if (dsString == "ds64")
+ {
+ //skip 4 bytes to read size of file in ds64 header
+ tmp = tree->digest32u(file, "", true);
+ XMP_Uns64 dsHeader = tree->digest64u(file, "", true);
+ dsHeaderSize = GetUns64BE(&dsHeader);
+ }
+ tree->pushNode(chunkPath);
+ tree->addComment("offset 0x%llX, size 0x%llX, size(w/o header) 0x%llX", chunkPos, chunkSize, chunkSizeWOHeader);
+
+ tree->setKeyValue("dsChunkSize", fromArgs("0x%llX", dsHeaderSize + 8));
+
+ }
+ else
+ {
+ tree->pushNode(chunkPath);
+ tree->addComment("offset 0x%llX, size 0x%llX, size(w/o header) 0x%llX", chunkPos, chunkSize, chunkSizeWOHeader);
+
+ }
+ tree->setKeyValue("fileSize", fromArgs("0x%llX", fileSize));
+ tree->setKeyValue("validRF64Header", fromArgs("0x%llX", validRF64Header));
+
+
+ if (isOutermost && idString == "RF64")
+ {
+ tree->pushNode("RF64/ds64");
+ tree->addComment("offset 0x%llX, size 0x%X, size(w/o header) 0x%X", chunkPos + 12, rf64Sizes->chunkSize + 8, rf64Sizes->chunkSize);
+ tree->setKeyValue("riffSize", fromArgs("0x%llX", rf64Sizes->riffSize));
+ tree->setKeyValue("dataSize", fromArgs("0x%llX", rf64Sizes->dataSize));
+ tree->setKeyValue("sampleCount", fromArgs("0x%llX", rf64Sizes->sampleCount));
+ tree->setKeyValue("tableLength", fromArgs("0x%X", rf64Sizes->tableLength));
+ tree->popNode();
+
+ DumpRIFFChunk(file, LFA_Tell(file) + chunkSize - 12 - rf64Sizes->chunkSize - 8 /* filesize + riff chunk size - riff header(12) - rf64 header(8) */, chunkPath, bigEndian, rf64Sizes); // recurse!
+ }
+ if ((idString + ":" + typeString == "LIST:INFO") ||
+ (idString + ":" + typeString == "LIST:Tdat") ||
+ (idString + ":" + typeString == "RIFF:AVI ") ||
+ (idString + ":" + typeString == "RIFF:AVIX") ||
+ (idString + ":" + typeString == "RIFF:WAVE") ||
+ (idString + ":" + typeString == "FORM:AIFF") ||
+ (idString + ":" + typeString == "FORM:AIFC") ||
+ (idString + ":" + typeString == "LIST:hdrl") ||
+ (idString + ":" + typeString == "LIST:strl") ||
+ (idString + ":" + typeString == "LIST:movi")
+ )
+ {
+ DumpRIFFChunk(file, LFA_Tell(file) + chunkSize - 12, chunkPath, bigEndian, rf64Sizes); // recurse!
+ }
+ else
+ {
+ Skip(file, chunkSize - 12); // skip it !
+ }
+ tree->popNode();
+ }
+ else if (idString.length() == 4) // check that we got a valid idString
+ {
+
+ // now that LIST:movi gets dumped,
+ // skip some very frequent, irrelevant chunks,
+ // otherwise the dump becomes unusably long...
+ std::string firstTwo = idString.substr(0, 2);
+ std::string secondTwo = idString.substr(2, 2);
+ if (secondTwo == "db" || secondTwo == "dc" || secondTwo == "wb") // nb: _could_ colidde, requiring additional numeric test
+ {
+ skipper = true;
+ }
+
+ if (!skipper)
+ {
+ tree->pushNode(chunkPath);
+ //Log::info( chunkPath );
+ tree->addComment("offset 0x%llX, size 0x%llX, size(w/o header) 0x%llX", chunkPos, chunkSize, chunkSizeWOHeader);
+ }
+
+ // tackle chunks of interest //////////////////////////////////////////////
+ bool isListInfo =
+ ((origChunkPath == "RIFF:WAVE/LIST:INFO" || origChunkPath == "RIFF:AVI /LIST:INFO")
+ && idString.at(0) == 'I'); // so far all mapping relevant props begin with "I"
+
+ bool isListTdat = (origChunkPath == "RIFF:WAVE/LIST:Tdat" || origChunkPath == "RIFF:AVI /LIST:Tdat")
+ && idString.at(0) != 'J'; // just exclude JUNK/Q
+
+ bool isDispChunk =
+ ((origChunkPath == "RIFF:WAVE" || origChunkPath == "RIFF:AVI ")
+ && idString == "DISP");
+
+ bool isBextChunk =
+ ((origChunkPath == "RIFF:WAVE" || origChunkPath == "RIFF:AVI ")
+ && idString == "bext");
+
+ bool isIXMLChunk =
+ ((origChunkPath == "RIFF:WAVE")
+ && idString == "iXML");
+
+ bool isXMPchunk = false; //assume beforehand
+ if (idString == "_PMX")
+ { // detour first, to detect xmp in wrong places
+ assertMsg("XMP packet found in wrong place!",
+ (origChunkPath == "RIFF:WAVE" || "RIFF:AVI" || "RIFF:AVIX")); //be very linient here.
+ isXMPchunk = true;
+ }
+
+ bool isIDITChunk =
+ ((origChunkPath == "RIFF:AVI/LIST:hdrl" || origChunkPath == "RIFF:AVI /LIST:hdrl")
+ && idString == "IDIT");
+
+ // deal with chunks of interest /////////////////////////////////////////////
+ // a little prelude for disp chunk
+ if (isDispChunk)
+ {
+ XMP_Uns32 dispChunkType = LFA_ReadUns32_LE(file);
+ // only dispChunks starting with a 0x0001 are of interest to us.
+ // others do exist and are not an error
+
+ if (dispChunkType != 0x0001)
+ isDispChunk = false;
+
+ chunkSize -= 4;
+ }
+
+ if (isListInfo || isListTdat || isDispChunk || isIDITChunk)
+ {
+ // dump that string:
+ std::string value;
+
+ if (chunkSize > 8) // aka skip for empty chunks
+ {
+ // first check if the string is zero terminated
+ LFA_Seek(file, chunkSize - 8 - 1, SEEK_CUR); // jump to last char
+ bool zeroTerm = (LFA_ReadUns8(file) == 0);
+ LFA_Seek(file, -(chunkSize - 8), SEEK_CUR); //jump back
+ // some strings are zero-terminated (so despite initial length they are "c-strings"
+ // others are not ( "pascal strings" if you will.
+ // must cater to both: zero-terminated-ness should not affect resulting value.
+ if (zeroTerm)
+ {
+ // read string without zero (last char)
+ value = tree->digestString(file, "", chunkSize - 8 - 1, false);
+ tree->addComment(" zero terminated");
+ LFA_ReadUns8(file); // skip the zero
+ }
+ else
+ {
+ // read string including last char
+ value = tree->digestString(file, "", chunkSize - 8, false);
+ tree->addComment(" not zero terminated");
+ }
+ tree->changeValue(value);
+ }
+
+ tree->changeValue(value);
+ }
+ else if (isXMPchunk)
+ {
+ tree->pushNode("XMP packet");
+
+ tree->addOffset(file);
+ tree->addComment("packet size: 0x%llX", chunkSize - 8);
+ Skip(file, chunkSize - 8);
+ tree->addComment("packet end: 0x%llX", LFA_Tell(file));
+
+ tree->popNode();
+ }
+ else if (isBextChunk)
+ {
+ tree->pushNode("bext chunk");
+ tree->addOffset(file);
+ tree->addComment("packet size: 0x%llX", chunkSize - 8);
+
+ // I assume that the minimum BEXT chunk size is 602:
+ // > 8 + ( 256+32+32+10+8+4+4+2+64+190+0 )
+ // ans = 610
+ const XMP_Int64 MIN_BEXT_SIZE = 610;
+ assertMsg("minimum Berx Chunk Size", chunkSize >= MIN_BEXT_SIZE);
+ XMP_Int64 BEXT_CodingHistorySize = chunkSize - MIN_BEXT_SIZE;
+
+ setFixedBEXTField(file, chunkPath + ".Description", 256);
+ setFixedBEXTField(file, chunkPath + ".Originator", 32);
+ setFixedBEXTField(file, chunkPath + ".OriginatorReference", 32);
+ setFixedBEXTField(file, chunkPath + ".OriginationDate", 10);
+ setFixedBEXTField(file, chunkPath + ".OriginationTime", 8);
+
+ tree->digest32u(file, chunkPath + ".TimeReferenceLow", false, true); // DWORD == 32 Bit
+ tree->digest32u(file, chunkPath + ".TimeReferenceHigh", false, true); // DWORD == 32 Bit
+
+ tree->digest16u(file, chunkPath + ".Version", false, true);
+
+ // UMID has 64 bytes:
+ tree->digestString(file, chunkPath + ".UMID", 64);
+ //tree->digest32u( file, chunkPath+".UMID_0-4", false, true );
+ //tree->setKeyValue( "UMID_5-59" );
+ //Skip( file, 64 - 4 - 4 );
+ //tree->digest32u( file, chunkPath+".UMID_60-63", false, true );
+
+ tree->setKeyValue(chunkPath + ".Reserved");
+ Skip(file, 190);
+
+ if (BEXT_CodingHistorySize)
+ {
+ setFixedBEXTField(file, chunkPath + ".CodingHistory", BEXT_CodingHistorySize);
+
+ //tree->setKeyValue( chunkPath+".CodingHistory" ); // not bothering details.
+ tree->addComment("( 0x%llx bytes ) ", BEXT_CodingHistorySize);
+ //Skip( file, BEXT_CodingHistorySize );
+ }
+
+ tree->addComment("packet end: 0x%llX", LFA_Tell(file));
+ tree->popNode();
+ }
+ else if (isIXMLChunk) {
+ tree->pushNode("iXML packet");
+
+ tree->addOffset(file);
+ tree->addComment("packet size: 0x%llX", chunkSize - 8);
+ //Skip( file, chunkSize - 8 );
+
+ size_t sizeofIXMLValue = chunkSize - 8;
+ char* descriptionBuffer = new char[sizeofIXMLValue + 2];
+ LFA_Read(file, descriptionBuffer, sizeofIXMLValue, true);
+ descriptionBuffer[sizeofIXMLValue] = '\0'; // tack on, in case not contained
+ // parse till first \0
+ std::string description(descriptionBuffer);
+
+ // Dumping the iXML chunk. Needed for testing
+ // Add iXML chunk as a node to tree
+ tree->setKeyValue(chunkPath + ".ValueOfIXMLChunk", description);
+
+ delete[] descriptionBuffer;
+ tree->addComment("packet end: 0x%llX", LFA_Tell(file));
+
+ tree->popNode();
+
+ }
+ else
+ {
+ Skip(file, chunkSize - 8); // skip remainder of chunk ( id, length already digested )
+ assertMsg(fromArgs("inner chunk size too big, curPos:0x%llx, parentEnd:0x%llx",
+ LFA_Tell(file),
+ parentEnd),
+ LFA_Tell(file) <= parentEnd);
+ }
+
+ if (!skipper)
+ tree->popNode();
+ }
+ else
+ {
+ //dump undefined bytes in LIST
+ tree->pushNode("** unknown bytes **");
+ tree->addOffset(file);
+ tree->addComment("size: 0x%llX", chunkSize);
+ Skip(file, chunkSize - 8);
+ tree->popNode();
+ }
+
+ if (LFA_Tell(file) % 2 == 1) // if odd file position, add pad byte.
+ {
+ if (LFA_Tell(file) == parentEnd)
+ {
+ // last pad byte is missing
+ tree->addComment(" (pad byte missing [bug 1521093])");
+ }
+ else
+ {
+ XMP_Uns8 padByte = LFA_ReadUns8(file);
+ if (!skipper)
+ {
+ if (0 != padByte)
+ tree->addComment(" (non-zero pad byte!)");
+ else
+ tree->addComment(" (pad byte)");
+ }
+ }
+ }
+ }
+ }
+ } // while
+
+
+} // DumpRIFFChunk
+
+ // =================================================================================================
+
+static void
+DumpRIFF(LFA_FileRef file, XMP_Int64 fileLen)
+{
+ DataSize64Chunk rf64Sizes;
+ DumpRIFFChunk(file, fileLen, "", false, &rf64Sizes);
+}
+
+static void
+DumpAIFF(LFA_FileRef file, XMP_Int64 fileLen)
+{
+ DumpRIFFChunk(file, fileLen, "", true);
+}
+// =================================================================================================
+
+static XMP_Uns32 crcTable[256];
+static bool crcTableInited = false;
+
+static XMP_Uns32 ComputeCRCforPNG(LFA_FileRef file, XMP_Uns32 crcOffset, XMP_Uns32 crcLen)
+{
+ if (!crcTableInited) {
+ for (int n = 0; n < 256; ++n) {
+ XMP_Uns32 c = n;
+ for (int k = 0; k < 8; ++k) {
+ XMP_Uns32 lowBit = c & 1;
+ c = c >> 1;
+ if (lowBit != 0) c = c ^ 0xEDB88320;
+ }
+ crcTable[n] = c;
+ }
+ crcTableInited = true;
+ }
+
+ XMP_Uns32 crc = 0xFFFFFFFF;
+ CaptureFileData(file, crcOffset, crcLen);
+
+ for (XMP_Uns32 i = 0; i < crcLen; ++i) { // ! The CRC includes the chunk type and data.
+ XMP_Uns8 byte = sDataPtr[i];
+ XMP_Uns8 index = (XMP_Uns8)((crc ^ byte) & 0xFF);
+ crc = crcTable[index] ^ (crc >> 8);
+ }
+
+ return crc ^ 0xFFFFFFFF;
+
+} // ComputeCRCforPNG
+
+ // =================================================================================================
+
+static const XMP_Uns32 kPNG_iTXt = 0x69545874;
+static const XMP_Uns32 kPNG_tEXt = 0x74455874;
+static const XMP_Uns32 kPNG_zTXt = 0x7A545874;
+
+static XMP_Uns32
+DumpPNGChunk(LFA_FileRef file, XMP_Uns32 pngLen, XMP_Uns32 chunkOffset)
+{
+ // A PNG chunk contains:
+ // A big endian UInt32 length for the data portion. Zero is OK.
+ // A 4 byte chunk type, should be 4 ASCII letters, lower or upper case.
+ // The chunk data.
+ // A big endian UInt32 CRC.
+ // There are no alignment constraints.
+ //
+ // Chunks of type tEXt, iTXt, and zTXt have text values. Each text form has a leading "usage
+ // keyword" followed by the data string. The keywords must be visible Latin-1, 0x20..0x7E and
+ // 0xA1..0xFF. They are limited to 1 to 79 characters, plus a terminating nul.
+ //
+ // A tEXt chunk has 0 or more bytes of Latin-1 characters. The data is not nul terminated, and
+ // embedded nuls are not allowed. A zTXt chunk is like tEXt but the data string is zlib compressed.
+ // An iTXt chunk has a variety of "info tags" followed by a UTF-8 data string.
+ //
+ // The XMP is in an iTXt chunk with the keyword XML:com.adobe.xmp and 4 bytes of 0 for the info.
+
+ XMP_Uns32 chunkLen;
+ XMP_Uns32 chunkType;
+ XMP_Uns32 chunkCRC;
+
+ if ((pngLen - chunkOffset) < 12) {
+ tree->addComment("** Unexpected end of PNG file, %ul bytes remaining **", (pngLen - chunkOffset));
+ return (pngLen - chunkOffset);
+ }
+
+ LFA_Seek(file, chunkOffset, SEEK_SET);
+ LFA_Read(file, &chunkLen, 4, true);
+ chunkLen = GetUns32BE(&chunkLen);
+
+ if (chunkLen > (pngLen - chunkOffset)) {
+ tree->addComment("** No room for PNG chunk, need %u, have %u **", chunkLen, pngLen - chunkOffset);
+ return (pngLen - chunkOffset); // ! Not chunkLen, might be bad and cause wrap-around.
+ }
+
+ LFA_Read(file, &chunkType, 4, true); // After read, memory is in file order.
+
+ LFA_Seek(file, (chunkOffset + 8 + chunkLen), SEEK_SET);
+ LFA_Read(file, &chunkCRC, 4, true);
+ chunkCRC = GetUns32BE(&chunkCRC);
+
+ tree->addComment(" '%.4s', offset %u (0x%X), size %d, CRC 0x%.8X",
+ &chunkType, chunkOffset, chunkOffset, chunkLen, chunkCRC);
+
+ XMP_Uns32 newCRC = ComputeCRCforPNG(file, (chunkOffset + 4), (chunkLen + 4));
+
+ if (chunkCRC != newCRC) tree->addComment("** CRC should be 0x%.8X **", newCRC);
+
+ chunkType = GetUns32BE(&chunkType); // Reorder the type to compare with constants.
+
+ if ((chunkType == kPNG_iTXt) || (chunkType == kPNG_tEXt) || (chunkType == kPNG_zTXt)) {
+
+ CaptureFileData(file, (chunkOffset + 8), chunkLen);
+
+ XMP_Uns8 * keywordPtr = sDataPtr;
+ size_t keywordLen = strlen((char*)keywordPtr);
+
+ PrintOnlyASCII_8(keywordPtr, keywordLen);
+
+ if ((chunkType == kPNG_iTXt) && (keywordLen == 17) && CheckBytes(keywordPtr, "XML:com.adobe.xmp", 18)) {
+
+ if (sXMPPtr != 0) {
+ tree->addComment(" ** Redundant XMP **");
+ }
+ else {
+ CaptureXMP((keywordPtr + 22), (chunkLen - 22), (chunkOffset + 8 + 22));
+ XMP_Uns32 otherFlags = GetUns32BE(keywordPtr + 18);
+ if (otherFlags != 0) tree->addComment("** bad flags %.8X **", otherFlags);
+ }
+
+ }
+
+ }
+
+ return (8 + chunkLen + 4);
+
+} // DumpPNGChunk
+
+ // =================================================================================================
+
+static void
+DumpPS(LFA_FileRef file, XMP_Uns32 fileLen)
+{
+ XMP_Int32 psOffset;
+ size_t psLength;
+
+ LFA_Seek(file, 4, SEEK_SET); // skip fileheader bytes
+ LFA_Read(file, &psOffset, 4, true);
+ LFA_Read(file, &psLength, 4, true);
+
+ tree->addComment(" psOffset: %d, psLength: %d", psOffset, psLength);
+
+ // jump to psOffset
+ Skip(file, (psOffset - 12));
+
+ // get the header (everything till first %
+
+ XMP_Int64 offset = LFA_Tell(file);
+ std::string key, value;
+ char byte = LFA_GetChar(file);
+ bool eof = false;
+ while (!eof)
+ {
+ key.clear();
+ key += byte; // add the first %
+ byte = LFA_GetChar(file);
+
+ while (byte != ' ' && byte != '\r') // get everthing until next space or LF
+ {
+ key += byte;
+ byte = LFA_GetChar(file);
+
+ }
+
+ //if (CheckBytes( key.c_str(), "%%EOF", 5))
+ if (key == "%%EOF")
+ {
+ eof = true;
+ }
+ else
+ {
+ byte = LFA_GetChar(file);
+ value.clear();
+ while (byte != '%') // get everthing until next %
+ {
+ value += byte;
+ byte = LFA_GetChar(file);
+ }
+ }
+ tree->pushNode(key);
+ tree->addOffset(file);
+
+ //for now only store value for header
+ if (key == "%!PS-Adobe-3.0")
+ {
+ tree->changeValue(value);
+ }
+
+ tree->addComment("offset: %d", offset);
+ tree->addComment("size: 0x%llX", LFA_Tell(file) - offset);
+ tree->popNode();
+
+ offset = LFA_Tell(file);
+ }
+ // Now just get everything else and store all keys that start with %
+
+
+ // get the key
+ // start of the PostScript DSC header comment
+
+ /*XMP_Uns8 buffer [11];
+ LFA_Read ( file, &buffer, sizeof(buffer), true );
+
+ if (!CheckBytes( buffer, "%!PS-Adobe-", 11))
+ {
+ tree->comment ( "** Invalid PS, unknown PS file tag." );
+ return;
+ }
+
+ // Check the PostScript DSC major version number.
+ XMP_Uns8 byte;
+ LFA_Read ( file, &byte, sizeof(byte), true );
+
+ psMajorVer = 0;
+ while ( IsNumeric( byte ) )
+ {
+ psMajorVer = (psMajorVer * 10) + (byte - '0');
+ if ( psMajorVer > 1000 ) {
+ tree->comment ( "** Invalid PS, Overflow." );
+ return;
+ }; // Overflow.
+ LFA_Read ( file, &byte, sizeof(byte), true );
+ }
+ if ( psMajorVer < 3 ){
+ tree->comment ( "** Invalid PS, The version must be at least 3.0." );
+ return;
+ }; // The version must be at least 3.0.
+
+ if ( byte != '.' ){
+ tree->comment ( "** Invalid PS, No minor number" );
+ return;
+ }; // No minor number.
+ LFA_Read ( file, &byte, sizeof(byte), true );
+
+ // Check the PostScript DSC minor version number.
+
+ psMinorVer = 0;
+ while ( IsNumeric( byte ) )
+ {
+ psMinorVer = (psMinorVer * 10) + (byte - '0');
+ if ( psMinorVer > 1000 ) {
+ tree->comment ( "** Invalid PS, Overflow." );
+ return;
+ }; // Overflow.
+ LFA_Read ( file, &byte, sizeof(byte), true );
+ }
+
+ tree->addComment(" psMajor Version: %d, psMinor Version: %d", psMajorVer, psMinorVer);*/
+}
+
+// =================================================================================================
+
+static void
+DumpPNG(LFA_FileRef file, XMP_Uns32 pngLen)
+{
+ // A PNG file contains an 8 byte signature followed by a sequence of chunks.
+
+ XMP_Uns32 chunkOffset = 8;
+
+ while (chunkOffset < pngLen) {
+ XMP_Uns32 chunkLen = DumpPNGChunk(file, pngLen, chunkOffset);
+ chunkOffset += chunkLen;
+ }
+
+ if (sXMPPtr != 0) DumpXMP("PNG XMP 'iTXt' chunk");
+
+} // DumpPNG
+
+ // =================================================================================================
+
+static void
+DumpInDesign(LFA_FileRef file, XMP_Uns32 inddLen)
+{
+ InDesignMasterPage masters[2];
+ size_t dbPages;
+ XMP_Uns8 cobjEndian;
+
+ // FIgure out which master page to use.
+
+ LFA_Seek(file, 0, SEEK_SET);
+ LFA_Read(file, &masters, sizeof(masters), true);
+
+ XMP_Uns64 seq0 = GetUns64LE((XMP_Uns8 *)&masters[0].fSequenceNumber);
+ XMP_Uns64 seq1 = GetUns64LE((XMP_Uns8 *)&masters[1].fSequenceNumber);
+
+ if (seq0 > seq1) {
+ dbPages = GetUns32LE((XMP_Uns8 *)&masters[0].fFilePages);
+ cobjEndian = masters[0].fObjectStreamEndian;
+ tree->addComment(" Using master page 0");
+ }
+ else {
+ dbPages = GetUns32LE((XMP_Uns8 *)&masters[1].fFilePages);
+ cobjEndian = masters[1].fObjectStreamEndian;
+ tree->addComment(" Using master page 1");
+ }
+
+ bool bigEndian = (cobjEndian == kINDD_BigEndian);
+
+ tree->addComment("%d pages, %s endian", dbPages, (bigEndian ? "big" : "little"));
+
+ // Look for the XMP contiguous object.
+
+ // *** XMP_Int64 cobjPos = (XMP_Int64)dbPages * kINDD_PageSize; // ! Use a 64 bit multiply!
+ XMP_Uns32 cobjPos = dbPages * kINDD_PageSize;
+ XMP_Uns32 cobjLen;
+
+ for (; cobjPos < inddLen; cobjPos += cobjLen) {
+
+ InDesignContigObjMarker cobjHead;
+
+ LFA_Seek(file, cobjPos, SEEK_SET);
+ LFA_Read(file, &cobjHead, sizeof(cobjHead), true);
+
+ if (!CheckBytes(&cobjHead.fGUID, kINDDContigObjHeaderGUID, kInDesignGUIDSize)) {
+
+ // No Contiguous Object header. Could be in zero padding for the last page.
+
+ XMP_Uns8 fileTail[kINDD_PageSize];
+ size_t tailLen = inddLen - cobjPos;
+ bool endOK = (tailLen < kINDD_PageSize);
+
+ if (endOK) {
+ LFA_Seek(file, cobjPos, SEEK_SET);
+ LFA_Read(file, fileTail, sizeof(fileTail), true);
+ for (size_t i = 0; i < tailLen; ++i) {
+ if (fileTail[i] != 0) {
+ endOK = false;
+ break;
+ }
+ }
+ }
+
+ if (endOK) break;
+ tree->addComment(" ** No Contiguous Object GUID at offset %u (0x%X) tree.", cobjPos, cobjPos);
+ return;
+
+ }
+
+ cobjHead.fObjectUID = GetUns32LE(&cobjHead.fObjectUID);
+ cobjHead.fObjectClassID = GetUns32LE(&cobjHead.fObjectClassID);
+ cobjHead.fStreamLength = GetUns32LE(&cobjHead.fStreamLength);
+ cobjHead.fChecksum = GetUns32LE(&cobjHead.fChecksum);
+
+ cobjLen = cobjHead.fStreamLength + (2 * sizeof(InDesignContigObjMarker));
+
+ tree->addComment(" ContigObj offset %d (0x%X), size %d, Object UID %.8X, class ID %.8X, checksum %.8X",
+ cobjPos, cobjPos, cobjHead.fStreamLength, cobjHead.fObjectUID, cobjHead.fObjectClassID, cobjHead.fChecksum);
+
+ if ((cobjHead.fObjectClassID & 0x40000000) == 0) tree->addComment("read only");
+
+ XMP_Uns32 xmpLen;
+ LFA_Read(file, &xmpLen, 4, true);
+ if (bigEndian) {
+ xmpLen = GetUns32BE(&xmpLen);
+ }
+ else {
+ xmpLen = GetUns32LE(&xmpLen);
+ }
+
+ XMP_Uns8 xmpStart[16]; // Enough for "<?xpacket begin=".
+ LFA_Read(file, &xmpStart, sizeof(xmpStart), true);
+
+ if ((cobjHead.fStreamLength > (4 + 16)) && ((xmpLen + 4) == cobjHead.fStreamLength) &&
+ CheckBytes(xmpStart, "<?xpacket begin=", 16)) {
+
+ if (sXMPPtr != 0) {
+ tree->addComment("** redundant XMP **");
+ }
+ else {
+ tree->addComment("XMP");
+ CaptureXMPF(file, (cobjPos + sizeof(InDesignContigObjMarker) + 4), xmpLen);
+ }
+
+ }
+
+ }
+
+ if (sXMPPtr != 0) DumpXMP("InDesign XMP Contiguous Object");
+
+} // DumpInDesign
+
+ // =================================================================================================
+
+static void
+DumpSVGTag(std::string basePath, XML_NodePtr currentNode)
+{
+ if (currentNode)
+ {
+ tree->pushNode(basePath + currentNode->name);
+
+ // Iterating over all XML children.
+ XML_NodeVector currNodeVector = currentNode->content;
+ for (size_t i = 0; i < currNodeVector.size(); i++)
+ {
+ // Dump all children who are element nodes.
+ if (currNodeVector[i]->kind == kElemNode)
+ DumpSVGTag(basePath + currentNode->name + "/", currNodeVector[i]);
+
+ // Extract the value from datanodes and put in TagMap if it's not yet available.
+ if (currNodeVector[i]->kind == kCDataNode && tree->getValue(basePath + currentNode->name) == "")
+ tree->updateKeyValue(basePath + currentNode->name, currNodeVector[i]->value);
+ }
+ }
+
+} // DumpSVGTag
+
+ // =================================================================================================
+
+static void
+DumpSVG(LFA_FileRef file, XMP_Uns32 svgLen)
+{
+ // SVG is an XML based format.We consider any file as SVG file if the given file contains a SVG tag.
+ // Hence CheckFileFormat looks for presence of "<svg" in the file.
+ //
+ // For Dumping SVG elements we are using ExpatAdapter. Below code will parse given file using this
+ // adapter and add different tags in the TagMap tree.
+ //
+ // Below is the currently supported structure of known tags for this format.
+ // <svg>
+ // <title/>
+ // <desc/>
+ // <metadata>
+ // <x:xmpmeta/>
+ // <...>
+ // <...>
+ // </metadata>
+ // <...>
+ // <...>
+ // </svg>
+
+ ExpatAdapter * pExpatAdapter = XMP_NewExpatAdapter(false);
+
+ if (pExpatAdapter == 0)
+ {
+ tree->comment("ExpatAdapter initialization failed. Cann't parse SVG file.");
+ return;
+ }
+
+ // Allocating big enough memory on heap to read file contents.
+ XMP_Uns8 *fileContent = new XMP_Uns8[svgLen + 1];
+ memset(fileContent, 0, (svgLen + 1) * sizeof(XMP_Uns8));
+
+ // Reading total file in buffer (fileContent)
+ LFA_Seek(file, 0, SEEK_SET);
+ LFA_Read(file, fileContent, svgLen + 1, false);
+
+ // Parsing the file with ExpatAdapter
+ pExpatAdapter->ParseBuffer(fileContent, svgLen + 1, false /* not the end */);
+
+ // Finding <svg> element and adding to TagMap tree.
+ XML_NodePtr svgNode = pExpatAdapter->tree.GetNamedElement("http://www.w3.org/2000/svg", "svg");
+ DumpSVGTag("", svgNode);
+
+ // De-allocating all the resources.
+ if (fileContent)
+ {
+ delete[] fileContent;
+ fileContent = NULL;
+ }
+
+ if (pExpatAdapter)
+ {
+ delete pExpatAdapter;
+ pExpatAdapter = NULL;
+ }
+
+} // DumpSVG
+
+ // =================================================================================================
+
+#define kSWF_FileAttributesTag 69
+#define kSWF_MetadataTag 77
+
+static void
+DumpSWF(LFA_FileRef file, XMP_Uns32 swfLen)
+{
+ // SWF is a chunky format, the chunks are called tags. The data of a tag cannot contain an
+ // offset to another tag, so tags can generally be freely inserted, removed, etc. Each tag has a
+ // header followed by data. There are short (2 byte) and long (6 byte) headers. A short header
+ // is a UInt16 with a type code in the upper 10 bits and data length in the lower 6 bits. The
+ // length does not include the header. A length of 63 (0x3F) indicates a long header. This adds
+ // an SInt32 data length.
+ //
+ // All multi-byte integers in SWF are little endian. Strings use byte storage and are null
+ // terminated. In SWF 5 or earlier strings use ASCII or shift-JIS encoding with no indication in
+ // the file. In SWF 6 or later strings use UTF-8.
+ //
+ // The overall structure of a SWF file:
+ // File header
+ // FileAttributes tag, optional before SWF 8
+ // other tags
+ // End tag
+ //
+ // The End tag is #0. No data is defined, but a reader should process the length normally. The
+ // last tag must be an End tag, but End tags can also be used elsewhere (e.g. to end a sprite
+ // definition). There is no standard tag for free or unused space.
+ //
+ // SWF file header:
+ // 0 3 - signature, "FWS"=uncompressed, 'CWS'=compressed (zlib, SWF6 or later)
+ // 3 1 - UInt8 major version
+ // 4 4 - UInt32 uncompressed file length
+ // 8 v - frame rectangle, variable size
+ // ? 2 - UInt16 frame rate
+ // ? 2 - UInt16 frame count
+ //
+ // FileAttributes tag, #69:
+ // 0 3 - reserved, must be 0
+ // 3 1 - HasMetadata, 0/1 Boolean
+ // 4 3 - reserved, must be 0
+ // 7 1 - UseNetwork, 0/1 Boolean
+ // 8 24 - reserved, must be 0
+ //
+ // The Metadata tag is #77. If present, the FileAttributes tag must also be present and
+ // HasMetadata must be set. The data is a string, must be XMP, should be as compact as possible.
+ //
+ // The frame rectangle is a packed sequence of 5 bit fields, with zero bits add as padding to a
+ // byte boundary. The first field is 5 bits long and gives the number of bits in each of the
+ // other 4 fields (0..31). The others are signed integers for the X min/max and Y min/max
+ // coordinates. The frame rectangle field is at least 2 bytes long, and at most 17 bytes long.
+
+ XMP_Uns8 buffer[100]; // Big enough, need 32 for file header and 38 for FileAttributes.
+ size_t ioCount;
+
+ // Dump the file header.
+
+ bool isCompressed = false;
+ bool hasMetadata = false;
+
+ XMP_Uns8 fileVersion;
+ XMP_Uns32 fullLength;
+ XMP_Uns8 rectBits;
+ XMP_Uns16 frameRate, frameCount;
+
+ ioCount = LFA_Read(file, buffer, sizeof(buffer), false);
+ if (ioCount < 14) {
+ tree->comment("** Invalid SWF, file header is too short.");
+ return;
+ }
+
+ if (CheckBytes(buffer, "CWS", 3)) {
+ isCompressed = true;
+ }
+ else if (!CheckBytes(buffer, "FWS", 3)) {
+ tree->comment("** Invalid SWF, unknown file header signature.");
+ return;
+ }
+
+ fileVersion = buffer[3];
+ fullLength = GetUns32LE(&buffer[4]);
+ rectBits = buffer[8] >> 3;
+
+ XMP_Uns32 rectBytes = ((5 + (4 * rectBits)) / 8) + 1;
+ XMP_Uns32 headerBytes = 8 + rectBytes + 4;
+
+ if (ioCount < headerBytes) {
+ tree->comment("** Invalid SWF, file header is too short.");
+ return;
+ }
+
+ frameRate = GetUns16LE(&buffer[8 + rectBytes]);
+ frameCount = GetUns16LE(&buffer[8 + rectBytes + 2]);
+
+ // *** Someday decode the frame rectangle.
+
+ tree->pushNode("File Header");
+ tree->addComment("%scompressed, version %d, full length %d, frame rate %d, frame count %d",
+ (isCompressed ? "" : "un"), fileVersion, fullLength, frameRate, frameCount);
+ tree->popNode();
+ if (isCompressed) {
+ // *** Add support to decompress into a temp file.
+ tree->comment("** Ignoring compressed SWF contents.");
+ return;
+ }
+
+ // Dump the tags in the body of the file.
+
+ XMP_Uns16 tagType;
+ XMP_Uns32 tagOffset, tagLength, headerLength, dataLength;
+
+ for (tagOffset = headerBytes; (tagOffset < swfLen); tagOffset += tagLength) {
+
+ // Read the tag header, extract the type and data length.
+
+ LFA_Seek(file, tagOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, sizeof(buffer), false);
+
+
+ if (ioCount < 2) {
+ tree->comment("** Invalid SWF, tag header is too short at offset %u (0x%X).", tagOffset, tagOffset);
+ break;
+ }
+
+ tagType = GetUns16LE(&buffer[0]);
+ dataLength = tagType & 0x3F;
+ tagType = tagType >> 6;
+
+ if (dataLength < 63) {
+ headerLength = 2;
+ }
+ else {
+ if (ioCount < 6) {
+ tree->comment("** Invalid SWF, tag header is too short at offset %u (0x%X).", tagOffset, tagOffset);
+ break;
+ }
+ headerLength = 6;
+ dataLength = GetUns32LE(&buffer[2]);
+ }
+ tagLength = headerLength + dataLength;
+
+ // Make sure the tag fits in the file, being careful about arithmetic overflow.
+
+ if (tagLength > (swfLen - tagOffset)) {
+ tree->comment("** Invalid SWF, tag is too long at offset %u (0x%X).", tagOffset, tagOffset);
+ break;
+ }
+
+ // See if this is the FileAttributes tag or the Metadata tag.
+
+ if ((tagOffset == headerBytes) && (tagType != kSWF_FileAttributesTag) && (fileVersion >= 8)) {
+ tree->comment("** Invalid SWF, first tag is not FileAttributes.");
+ }
+
+ if (tagType == kSWF_FileAttributesTag) {
+
+ if (dataLength < 4) {
+ tree->comment("** Invalid SWF, FileAttributes tag is too short at offset %u (0x%X).", tagOffset, tagOffset);
+ continue;
+ }
+
+ XMP_Uns32 xmpFlag = GetUns32LE(&(buffer[headerLength])) & 0x10;
+ if (xmpFlag != 0) {
+ hasMetadata = true;
+ }
+
+ tree->pushNode("FileAttributes tag");
+ tree->addComment("Offset %d (0x%X), %s XMP", tagOffset, tagOffset, (hasMetadata ? "has" : "no"));
+ tree->popNode();
+
+ }
+ else if (tagType == kSWF_MetadataTag) {
+
+ if (!hasMetadata) {
+ tree->comment("** Invalid SWF, Metadata tag without HasMetadata flag at offset %u (0x%X).", tagOffset, tagOffset);
+ continue;
+ }
+
+ tree->pushNode("Metadata tag");
+ tree->addComment("Offset %d (0x%X)", tagOffset, tagOffset);
+ tree->popNode();
+
+ if (sXMPPtr != 0) {
+ tree->comment(" ** Redundant Metadata tag");
+ }
+ else {
+ CaptureXMPF(file, (tagOffset + headerLength), dataLength);
+ }
+
+ //if ( sXMPPtr != 0 ) DumpXMP ( "SWF Metadata tag (#77) XMP" );
+
+ }
+ else {
+ tree->pushNode("tag #%d", tagType);
+ tree->addComment("Offset %d (0x%X)", tagOffset, tagOffset);
+ tree->popNode();
+ }
+
+ }
+
+} // DumpSWF
+
+ // =================================================================================================
+
+static XMP_Uns32 DumpScriptDataArray(LFA_FileRef file, XMP_Uns32 sdOffset, XMP_Uns32 count,
+ bool isStrict, bool isOnXMP = false);
+static XMP_Uns32 DumpScriptDataObject(LFA_FileRef file, XMP_Uns32 sdOffset);
+static XMP_Uns32 DumpScriptDataObjectList(LFA_FileRef file, XMP_Uns32 sdOffset);
+
+static inline XMP_Uns32 GetUns24BE(XMP_Uns8 * ptr)
+{
+ return (GetUns32BE(ptr) >> 8);
+}
+
+#define ReadSDValue(len) \
+ ioCount = LFA_Read ( file, buffer, len,true); \
+ if ( ioCount != len ) { \
+ tree->comment ( "** Failure reading ScriptDataValue, ioCount = %d", ioCount ); \
+ return (sdOffset + 1 + ioCount); \
+ }
+
+
+static XMP_Uns32 DumpScriptDataValue(LFA_FileRef file, XMP_Uns32 sdOffset, bool isOnXMP = false)
+{
+ XMP_Uns8 buffer[64 * 1024];
+ size_t ioCount;
+
+ XMP_Uns8 kind;
+ XMP_Uns16 u16;
+ XMP_Uns32 u32;
+ XMP_Uns64 u64;
+
+ LFA_Seek(file, sdOffset, SEEK_SET);
+ ioCount = LFA_Read(file, &kind, 1, true);
+ if (ioCount != 1) {
+ tree->comment("** Failure reading ScriptDataValue kind, ioCount = %d", ioCount);
+ return sdOffset;
+ }
+
+ if (isOnXMP) {
+ if ((kind != 8) && (kind != 2) && (kind != 0xC)) {
+ tree->comment("** Invalid kind for onXMPData tag **");
+ }
+ }
+
+ XMP_Uns64 time;
+ XMP_Int16 tz;
+
+ switch (kind) {
+
+ case 0x00: // A number, IEEE double.
+ ReadSDValue(8);
+ u64 = GetUns64BE(&buffer[0]);
+ tree->addComment("float = %f", *((double*)(&u64)));
+ return (sdOffset + 1 + 8);
+
+ case 0x01: // A 0/1 Boolean. (??? general Uns8?)
+ ReadSDValue(1);
+ tree->addComment("bool = %d", buffer[0]);
+ return (sdOffset + 1 + 1);
+
+ case 0x02: // A short UTF-8 string, leading 2 byte count.
+ ReadSDValue(2);
+ u16 = GetUns16BE(&buffer[0]);
+ ReadSDValue(u16);
+ if (int(u16) < 4096)
+ {
+ tree->addComment("string (%d) = \"%.*s\"", u16, u16, buffer);
+ }
+ else {
+ tree->addComment("string (%d) ", u16);
+ }
+ if (buffer[u16 - 1] != 0) tree->addComment("value lacks trailing nul");
+ if (isOnXMP) CaptureXMPF(file, (sdOffset + 1 + 2), u16);
+ return (sdOffset + 1 + 2 + u16);
+
+ case 0x03: // An object list, triples of 0x02/key/value, ends at 0x02000009.
+ tree->addComment("object list");
+ return DumpScriptDataObjectList(file, sdOffset + 1);
+
+ case 0x04: // A movie clip path as short UTF-8 string
+ ReadSDValue(2);
+ u16 = GetUns16BE(&buffer[0]);
+ ReadSDValue(u16);
+ tree->addComment("movie (%d) = \"%.*s\"", u16, u16, buffer);
+ if (buffer[u16 - 1] != 0) tree->addComment("value lacks trailing nul");
+ return (sdOffset + 1 + 2 + u16);
+
+ case 0x05: // A null, single byte.
+ tree->addComment("null");
+ return (sdOffset + 1);
+
+ case 0x06: // A undefined, single byte.
+ tree->addComment("undefined");
+ return (sdOffset + 1);
+
+ case 0x07: // A reference, Uns16.
+ ReadSDValue(2);
+ u16 = GetUns16BE(&buffer[0]);
+ tree->addComment("reference = %d", u16);
+ return (sdOffset + 1 + 2);
+
+ case 0x08: // An ECMA array, 32-bit count then any number of key/value pairs. Has 0x000009 terminator.
+ ReadSDValue(4);
+ u32 = GetUns32BE(&buffer[0]);
+ tree->addComment("ECMA array [%d]", u32);
+ return DumpScriptDataArray(file, sdOffset + 1 + 4, u32, false, isOnXMP);
+
+ case 0x09: // End of object or array. Should not see this here!
+ tree->addComment("** end **");
+ return (sdOffset + 1);
+
+ case 0x0A: // A strict array, count then that many key/value pairs, no 0x000009 terminator.
+ ReadSDValue(4);
+ u32 = GetUns32BE(&buffer[0]);
+ tree->addComment("strict array [%d]", u32);
+ return DumpScriptDataArray(file, sdOffset + 1 + 4, u32, true);
+
+ case 0x0B: // A date, Uns64 milliseconds since Jan 1 1970, Int16 TZ offset in minutes.
+ ReadSDValue(10);
+ time = GetUns64BE(&buffer[0]);
+ tz = (XMP_Int16)GetUns16BE(&buffer[8]);
+ tree->addComment("date, time=%ULL, tz=%d", time, tz);
+ return (sdOffset + 1 + 10);
+
+ case 0x0C: // A long UTF-8 string, leading 4 byte count.
+ ReadSDValue(4);
+ u32 = GetUns32BE(&buffer[0]);
+ if (u32 < sizeof(buffer)) {
+ ReadSDValue(u32);
+ tree->addComment("long string (%d) = \"%.*s\"", u32, u32, buffer);
+ if (buffer[u32 - 1] != 0) tree->addComment("value lacks trailing nul");
+ }
+ else {
+ ReadSDValue(sizeof(buffer));
+ tree->addComment("long string (%d) = \"%.*s\"", u32, sizeof(buffer), buffer);
+ tree->comment("** truncated long string output **");
+ }
+ if (isOnXMP) CaptureXMPF(file, (sdOffset + 1 + 4), u32);
+ return (sdOffset + 1 + 4 + u32);
+
+ case 0x0D: // Unsupported, single byte.
+ tree->addComment("unsupported");
+ return (sdOffset + 1);
+
+ case 0x0E: // A RecordSet. (???)
+ tree->addComment("** record set ?? **");
+ return (sdOffset + 1);
+
+ case 0x0F: // XML as a long UTF-8 string
+ ReadSDValue(4);
+ u32 = GetUns32BE(&buffer[0]);
+ if (u32 < sizeof(buffer)) {
+ ReadSDValue(u32);
+ tree->addComment("XML (%d) = \"%.*s\"", u32, u32, buffer);
+ if (buffer[u32 - 1] != 0) tree->addComment("value lacks trailing nul");
+ }
+ else {
+ ReadSDValue(sizeof(buffer));
+ tree->addComment("XML (%d) = \"%.*s\"", u32, sizeof(buffer), buffer);
+ tree->comment("** truncated long string output **");
+ }
+ if (isOnXMP) CaptureXMPF(file, (sdOffset + 1 + 4), u32);
+ return (sdOffset + 1 + 4 + u32);
+
+ case 0x10: // A typed object list, short string class name, object list (like case 0x03).
+ ReadSDValue(2);
+ u16 = GetUns16BE(&buffer[0]);
+ ReadSDValue(u16);
+ tree->addComment("class, name = %.*s", u16, u16, buffer);
+ if (buffer[u16 - 1] == 0) tree->addComment("name has trailing nul");
+ return DumpScriptDataObjectList(file, (sdOffset + 1 + 2 + u16));
+
+ case 0x11: // AMF 3 data. (???)
+ tree->addComment("** AMF 3 data ?? **");
+ return (sdOffset + 1);
+
+ default:
+ tree->addComment("** unknown kind = %d **", kind);
+ return (sdOffset + 1);
+
+ }
+
+} // DumpScriptDataValue
+
+ // =================================================================================================
+
+static XMP_Uns32 DumpScriptVariable(LFA_FileRef file, XMP_Uns32 sdOffset, bool isOnXMP = false)
+{
+ XMP_Uns8 buffer[64 * 1024];
+ size_t ioCount;
+
+ LFA_Seek(file, sdOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 2, true);
+ if (ioCount != 2) {
+ tree->comment("** Failure reading DumpScriptVariable start, ioCount = %d", ioCount);
+ return (sdOffset + ioCount);
+ }
+
+ XMP_Uns16 nameLen = GetUns16BE(&buffer[0]);
+ ioCount = LFA_Read(file, buffer, nameLen, true);
+ if (ioCount != nameLen) {
+ tree->comment("** Failure reading ScriptDataObject name, ioCount = %d", ioCount);
+ return (sdOffset + 3 + ioCount);
+ }
+
+ tree->pushNode("%.*s @ 0x%X", nameLen, buffer, sdOffset);
+ if (buffer[nameLen - 1] == 0) tree->addComment("name has trailing nul");
+ if (strncmp((char*)buffer, "liveXML", nameLen) != 0) isOnXMP = false; // ! Else keep the input value.
+ XMP_Uns32 nextOffset = DumpScriptDataValue(file, (sdOffset + 2 + nameLen), isOnXMP);
+ tree->popNode();
+
+ return nextOffset;
+
+} // DumpScriptVariable
+
+ // =================================================================================================
+
+static XMP_Uns32 DumpScriptDataArray(LFA_FileRef file, XMP_Uns32 sdOffset, XMP_Uns32 headerCount,
+ bool isStrict, bool isOnXMP /* = false */)
+{
+ XMP_Uns8 buffer[3];
+ size_t ioCount;
+
+ XMP_Uns32 actualCount = 0;
+ XMP_Uns32 currOffset = sdOffset;
+
+ if (isStrict) {
+
+ for (; headerCount > 0; --headerCount) {
+ XMP_Uns32 nextOffset = DumpScriptVariable(file, currOffset);
+ if (nextOffset == currOffset) {
+ tree->comment("** Failure reading DumpScriptDataArray, no progress");
+ return currOffset;
+ }
+ currOffset = nextOffset;
+ }
+
+ }
+ else {
+
+ while (true) {
+
+ LFA_Seek(file, currOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 3, true);
+ if (ioCount != 3) {
+ tree->comment("** Failure check DumpScriptDataArray, ioCount = %d", ioCount);
+ return (currOffset + ioCount);
+ }
+ if (GetUns24BE(&buffer[0]) == 9) break;
+
+ XMP_Uns32 nextOffset = DumpScriptVariable(file, currOffset, isOnXMP);
+ if (nextOffset == currOffset) {
+ tree->comment("** Failure reading DumpScriptDataArray, no progress");
+ return currOffset;
+ }
+
+ ++actualCount;
+ currOffset = nextOffset;
+
+ }
+
+ if ((headerCount != (XMP_Uns32)(-1)) && (headerCount != actualCount)) {
+ tree->comment("Count mismatch, actual = %d", actualCount); // ! Not an error!
+ }
+
+ currOffset += 3; // ! Include the 0x000009 terminator.
+
+ }
+
+ return currOffset;
+
+} // DumpScriptDataArray
+
+ // =================================================================================================
+
+static XMP_Uns32 DumpScriptDataObject(LFA_FileRef file, XMP_Uns32 sdOffset)
+{
+ XMP_Uns8 buffer[64 * 1024];
+ size_t ioCount;
+
+ LFA_Seek(file, sdOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 2, true);
+ if (ioCount != 2) {
+ tree->comment("** Failure reading ScriptDataObject name length, ioCount = %d", ioCount);
+ return (sdOffset + ioCount);
+ }
+
+ XMP_Uns16 nameLen = GetUns16BE(&buffer[1]);
+ ioCount = LFA_Read(file, buffer, nameLen, true);
+ if (ioCount != nameLen) {
+ tree->comment("** Failure reading ScriptDataObject name, ioCount = %d", ioCount);
+ return (sdOffset + 2 + ioCount);
+ }
+
+ tree->pushNode("%.*s @ 0x%X", nameLen, buffer, sdOffset);
+ if (buffer[nameLen - 1] == 0) tree->addComment("name has trailing nul");
+ XMP_Uns32 nextOffset = DumpScriptDataValue(file, (sdOffset + 2 + nameLen));
+ tree->popNode();
+
+ return nextOffset;
+
+} // DumpScriptDataObject
+
+ // =================================================================================================
+
+static XMP_Uns32 DumpScriptDataObjectList(LFA_FileRef file, XMP_Uns32 sdOffset)
+{
+ XMP_Uns8 buffer[3];
+ size_t ioCount;
+
+ XMP_Uns32 currOffset = sdOffset;
+
+ while (true) {
+
+ LFA_Seek(file, currOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 3, true);
+ if (ioCount != 3) {
+ tree->comment("** Failure check ScriptDataObjectList, ioCount = %d", ioCount);
+ return (currOffset + ioCount);
+ }
+
+ XMP_Uns32 endFlag = GetUns24BE(&buffer[0]);
+ if (endFlag == 9) return (currOffset + 3);
+
+ XMP_Uns32 nextOffset = DumpScriptDataObject(file, currOffset);
+ if (nextOffset == currOffset) {
+ tree->comment("** Failure reading ScriptDataObjectList, no progress");
+ return currOffset;
+ }
+
+ currOffset = nextOffset;
+
+ }
+
+} // DumpScriptDataObjectList
+
+ // =================================================================================================
+
+static XMP_Uns32 DumpScriptDataTagContent(LFA_FileRef file, XMP_Uns32 sdOffset, XMP_Uns32 tagTime)
+{
+ XMP_Uns8 buffer[64 * 1024];
+ size_t ioCount;
+
+ LFA_Seek(file, sdOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 3, true);
+ if ((ioCount != 3) || (buffer[0] != 2)) {
+ tree->comment("** Failure reading ScriptDataObject start, ioCount = %d, buffer[0]=0x%X", ioCount, buffer[0]);
+ return (sdOffset + ioCount);
+ }
+
+ XMP_Uns16 nameLen = GetUns16BE(&buffer[1]);
+ ioCount = LFA_Read(file, buffer, nameLen, true);
+ if (ioCount != nameLen) {
+ tree->comment("** Failure reading ScriptDataObject name, ioCount = %d", ioCount);
+ return (sdOffset + 3 + ioCount);
+ }
+
+ tree->addComment("%.*s @ 0x%X", nameLen, buffer, sdOffset);
+ if (buffer[nameLen - 1] == 0) tree->addComment("name has trailing nul");
+
+ bool isOnXMP = (tagTime == 0) && (nameLen == 9) && (strncmp((char*)buffer, "onXMPData", 9) == 0);
+ return DumpScriptDataValue(file, (sdOffset + 1 + 2 + nameLen), isOnXMP);
+
+} // DumpScriptDataTagContent
+
+ // =================================================================================================
+
+static void
+DumpFLV(LFA_FileRef file, XMP_Uns32 flvLen)
+{
+ // FLV must not be confused with SWF, they are quite different internally. FLV is a chunky
+ // format, the chunks are called tags. All multi-byte integers in FLV are stored in big endian
+ // order. An FLV file begins with a variable length file header followed by a sequence of tags
+ // (the file body).
+ //
+ // Each tag contains a header, content, and back-pointer. The size in a tag header is just the
+ // data size. The back-pointer is the full size of the preceeding tag, not just the data length.
+ // The first tag is preceeded by a 0 back-pointer (not by the length of the header). The last
+ // tagk is followed by a back pointer.
+ //
+ // The FLV file header:
+ // 0 3 - signature, "FLV"
+ // 3 1 - UInt8 major version
+ // 4 1 - UInt8 flags
+ // 5 4 - UInt32 size of header (offset to body)
+ //
+ // The FLV tag header:
+ // 0 1 - UInt8 tag type
+ // 1 3 - UInt24 length of data
+ // 4 3 - UInt24 timestamp, milliseconds into the playback
+ // 7 1 - UInt8 timestamp high, for a full UInt32 playback time
+ // 8 3 - UInt24 stream ID, must be 0
+ //
+ // Only 3 tag types are defined by SWF-FLV-8, 8 = audio, 9 = video, 18 = script data. There is
+ // confusion or missing information in the spec about script data tags. In one place it uses the
+ // term "script data", in another SCRIPTDATAOBJECT for type 18. Then within the "Data Tags"
+ // section it talks about SCRIPTDATAOBJECT, SCRIPTDATAOBJECTEND, SCRIPTDATASTRING,
+ // SCRIPTDATALONGSTRING, SCRIPTDATAVALUE, SCRIPTDATAVARIABLE, SCRIPTDATAVARIABLEEND, and
+ // SCRIPTDATADATE. It isn't clear if these SCRIPTDATA* things are FLV tags, or substructure
+ // within the data of tag type 18.
+
+ XMP_Uns8 buffer[100];
+ size_t ioCount;
+ XMP_Uns32 size, time, stream, backSize;
+
+ ioCount = LFA_Read(file, buffer, 9 + 4, true);
+ if (ioCount != 9 + 4) {
+ tree->comment("** Failure reading FLV header, ioCount = %d", ioCount);
+ return;
+ }
+
+ size = GetUns32BE(&buffer[5]);
+ tree->addComment("FLV header: \"%.3s\", version %d, flags 0x%.2X, size %d (0x%X)",
+ &buffer[0], buffer[3], buffer[4], size);
+
+ LFA_Seek(file, size, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 4, true);
+ if (ioCount != 4) {
+ tree->comment("** Failure reading leading backSize, ioCount = %d", ioCount);
+ return;
+ }
+ backSize = GetUns32BE(&buffer[0]);
+ if (backSize != 0) {
+ tree->comment("** Bad leading backSize = %d", backSize);
+ return;
+ }
+
+ for (XMP_Uns32 tagOffset = (size + 4); tagOffset < flvLen; tagOffset += (11 + size + 4)) {
+
+ LFA_Seek(file, tagOffset, SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 11, true); // Back pointer plus tag header.
+ if (ioCount != 11) {
+ tree->comment("** Failure reading FLV tag, ioCount = %d", ioCount);
+ return;
+ }
+
+ size = GetUns24BE(&buffer[1]);
+ time = GetUns24BE(&buffer[4]);
+ time += ((XMP_Uns32)buffer[7] << 24);
+ stream = GetUns24BE(&buffer[8]);
+
+ if (time != 0) break; // ! Just do the time 0 tags for this tool.
+
+ char label[100];
+ char comment[1000];
+
+ XMP_Uns8 kind = buffer[0];
+ if (kind == 8) {
+ if (time == 0) sprintf(label, "Audio"); // Don't normally print, there are too many.
+ }
+ else if (kind == 9) {
+ if (time == 0) sprintf(label, "Video"); // Don't normally print, there are too many.
+ }
+ else if (kind == 18) {
+ sprintf(label, "ScriptData");
+ }
+ else {
+ sprintf(label, "<other-%d>", kind);
+ }
+ sprintf(comment, "%s @ 0x%X, size=%d, time=%d, stream=%d", label, tagOffset, size, time, stream);
+
+ tree->pushNode(label);
+ tree->addComment(comment);
+
+ LFA_Seek(file, (tagOffset + 11 + size), SEEK_SET);
+ ioCount = LFA_Read(file, buffer, 4, true); // Back pointer plus tag header.
+ if (ioCount != 4) {
+ tree->comment("** Failure reading backSize, ioCount = %d", ioCount);
+ return;
+ }
+
+ backSize = GetUns32BE(&buffer[0]);
+ if (backSize != (11 + size)) tree->comment("** Bad backSize= %d", backSize);
+
+ if (kind == 18) {
+ XMP_Uns32 endOffset = DumpScriptDataTagContent(file, (tagOffset + 11), time);
+ if (endOffset != (tagOffset + 11 + size)) {
+ tree->comment("** Bad endOffset = 0x%X", endOffset);
+ }
+ }
+
+ tree->popNode();
+
+ }
+
+ if (sXMPPtr != 0) DumpXMP("FLV onXMPData tag");
+
+} // DumpFLV
+
+ // =================================================================================================
+
+static bool
+PrintID3Encoding(XMP_Uns8 encoding, XMP_Uns8 * strPtr)
+{
+ bool bigEndian = true;
+
+ switch (encoding) {
+ case 0:
+ tree->addComment("Latin1");
+ break;
+ case 1:
+ if (*strPtr == 0xFF) bigEndian = false;
+ tree->addComment("UTF-16 with BOM, %s)", (bigEndian ? "BE" : "LE"));
+ break;
+ case 2:
+ tree->addComment("UTF-16 BE");
+ break;
+ case 3:
+ tree->addComment("UTF-8");
+ break;
+ default:
+ tree->addComment("** unknown **");
+ break;
+ }
+ return bigEndian;
+} // PrintID3Encoding
+
+ // =================================================================================================
+
+ //static void
+ //PrintID3EncodedText (XMP_Uns8 encoding, void * _strPtr, const char * label)
+ //{
+ //} // PrintID3EncodedText
+
+ // =================================================================================================
+
+struct ID3_Header {
+ char id3[3];
+ XMP_Uns8 vMajor, vMinor, flags;
+ XMP_Uns8 splitSize[4];
+};
+
+struct ID3_v22_FrameHeader {
+ char id[3];
+ XMP_Uns8 sizeHigh;
+ XMP_Uns16 sizeLow;
+};
+
+struct ID3_v23_FrameHeader {
+ char id[4];
+ XMP_Uns32 size;
+ XMP_Uns16 flags;
+};
+
+#define _U32b(ptr,n) ((XMP_Uns32) (((XMP_Uns8*)(ptr))[n]))
+#define GetSyncSafe32(ptr) ((_U32b(ptr,0) << 21) | (_U32b(ptr,1) << 14) | (_U32b(ptr,2) << 7) | _U32b(ptr,3))
+
+#define GetID3Size(ver,ptr) ((ver == 3) ? GetUns32BE(ptr) : GetSyncSafe32(ptr))
+
+// =================================================================================================
+
+static void DumpID3v22Frames(LFA_FileRef file, XMP_Uns8 vMajor, XMP_Uns32 framePos, XMP_Uns32 frameEnd) {
+
+ // Dump the frames in an ID3 v2.2 tag.
+
+ while ((framePos < frameEnd) && ((frameEnd - framePos) >= 6)) {
+
+ ID3_v22_FrameHeader frameHead;
+ LFA_Seek(file, framePos, SEEK_SET);
+ LFA_Read(file, &frameHead, sizeof(frameHead), true);
+
+ if (CheckBytes(frameHead.id, "\x0", 1)) break; // Assume into padding.
+ // FIXED: there could be just 1 or 2 or 3 bytes of padding total !!
+
+ XMP_Uns32 frameSize = ((XMP_Uns32)frameHead.sizeHigh << 16) + GetUns16BE(&frameHead.sizeLow);
+
+ tree->setKeyValue(
+ fromArgs("ID3v2:%.3s", frameHead.id), "", //no value yet, tree->changeValue() below
+ fromArgs("offset %d (0x%X), size %d", framePos, framePos, frameSize));
+
+ if (frameSize == 0) {
+
+ // NOTHING TO DO HERE.
+ // i.e. on 0-byte frames, including known ones...
+ // ( i.e. the testcase of a (errorneous) TCON 0 byte frame )
+
+ }
+ else if ((frameHead.id[0] == 'T') || (frameHead.id[0] == 'W')) { // Text and URL fields
+
+ CaptureFileData(file, 0, frameSize);
+ XMP_Uns8 encoding = 0;
+ XMP_Uns8 skip = 0;
+ if (frameHead.id[0] == 'T') { // URL field has no encoding byte
+ encoding = sDataPtr[0];
+ skip = 1;
+ }
+
+ bool bigEndian = PrintID3Encoding(encoding, (sDataPtr + skip));
+ if (encoding == 0) {
+ tree->changeValue(convert8Bit(sDataPtr + skip, false, frameSize - skip));
+ }
+ else {
+ tree->changeValue(convert16Bit(bigEndian, sDataPtr + skip, false, (frameSize - skip)));
+ }
+
+ }
+ else if (CheckBytes(frameHead.id, "PRV", 3) && (frameSize >= 4)) {
+
+ // checking on the XMP packet
+ CaptureFileData(file, 0, frameSize); //NB: has side effect: sDataLen, sDataMax, sDataPtr
+ tree->changeValue(convert8Bit(sDataPtr, false, strlen((char*)sDataPtr)));
+ if (CheckBytes(sDataPtr, "XMP\x0", 4)) {
+ CaptureXMPF(file, (framePos + sizeof(frameHead) + 4), (frameSize - 4));
+ }
+
+ }
+ else if (CheckBytes(frameHead.id, "COM", 3) || CheckBytes(frameHead.id, "ULT", 3)) {
+
+ const char * descrLabel = "ID3v2:COM-descr";
+ if (CheckBytes(frameHead.id, "ULT", 3)) descrLabel = "ID3v2:ULT-descr";
+
+ CaptureFileData(file, 0, frameSize);
+ XMP_Uns8 * frameEnd2 = sDataPtr + frameSize;
+
+ XMP_Uns8 encoding = sDataPtr[0];
+ char * lang = (char*)(sDataPtr + 1);
+
+ tree->addComment("lang '%.3s'", lang);
+ bool bigEndian = PrintID3Encoding(encoding, (sDataPtr + 4));
+
+ if (encoding == 0) {
+
+ XMP_Uns8 * descrPtr = sDataPtr + 4;
+ XMP_Uns8 * valuePtr = descrPtr;
+
+ while (*valuePtr != 0) ++valuePtr;
+ ++valuePtr;
+
+ size_t descrBytes = valuePtr - descrPtr - 1;
+ tree->changeValue(convert8Bit(valuePtr, false, frameEnd2 - valuePtr));
+ tree->setKeyValue(descrLabel, convert8Bit(descrPtr, false, descrBytes).c_str());
+
+ }
+ else {
+
+ XMP_Uns16 * descrPtr = (XMP_Uns16*)(sDataPtr + 4);
+ XMP_Uns16 * valuePtr = descrPtr;
+ while (*valuePtr != 0) ++valuePtr;
+ ++valuePtr;
+
+ size_t descrBytes = 2 * (valuePtr - descrPtr - 1);
+ size_t valueBytes = 2 * ((XMP_Uns16*)frameEnd2 - valuePtr);
+
+ tree->changeValue(convert16Bit(bigEndian, (XMP_Uns8*)valuePtr, false, valueBytes));
+ tree->setKeyValue(descrLabel, convert16Bit(bigEndian, (XMP_Uns8*)descrPtr, false, descrBytes).c_str());
+
+ }
+
+ }
+
+ framePos += (sizeof(frameHead) + frameSize);
+
+ }
+
+ if (framePos < frameEnd) {
+ tree->setKeyValue("", "",
+ fromArgs("Padding assumed, offset %d (0x%X), size %d", framePos, framePos, (frameEnd - framePos)));
+ }
+
+} // DumpID3v22Frames
+
+ // =================================================================================================
+
+static void DumpID3v23Frames(LFA_FileRef file, XMP_Uns8 vMajor, XMP_Uns32 framePos, XMP_Uns32 frameEnd) {
+
+ // Dump the frames in an ID3 v2.3 or v2.4 tag.
+ int iIterator = 0;
+
+ while ((framePos < frameEnd) && ((frameEnd - framePos) >= 10)) {
+
+ ID3_v23_FrameHeader frameHead;
+ LFA_Seek(file, framePos, SEEK_SET);
+ LFA_Read(file, &frameHead, sizeof(frameHead), true);
+
+ if (CheckBytes(frameHead.id, "\x0", 1)) break; // Assume into padding.
+ // FIXED: there could be just 1 or 2 or 3 bytes of padding total !!
+
+ frameHead.size = GetID3Size(vMajor, &frameHead.size);
+ frameHead.flags = GetUns16BE(&frameHead.flags);
+
+ tree->setKeyValue(
+ fromArgs("ID3v2:%.4s", frameHead.id), "", //no value yet, tree->changeValue() below
+ fromArgs("offset %d (0x%X), size %d, flags 0x%.2X", framePos, framePos, frameHead.size, frameHead.flags));
+
+ if (frameHead.size == 0) {
+
+ // NOTHING TO DO HERE.
+ // i.e. on 0-byte frames, including known ones...
+ // ( i.e. the testcase of a (errorneous) TCON 0 byte frame )
+
+ }
+ else if ((frameHead.id[0] == 'T') || (frameHead.id[0] == 'W')) { // Text and URL fields
+
+ CaptureFileData(file, 0, frameHead.size);
+ XMP_Uns8 encoding = 0;
+ XMP_Uns8 skip = 0;
+ if (frameHead.id[0] == 'T') { // URL field has no encoding byte
+ encoding = sDataPtr[0];
+ skip = 1;
+ }
+
+ bool bigEndian = PrintID3Encoding(encoding, (sDataPtr + skip));
+ if ((encoding == 0) || (encoding == 3)) {
+ tree->changeValue(convert8Bit(sDataPtr + skip, false, frameHead.size - skip));
+ }
+ else if ((encoding == 1) || (encoding == 2)) {
+ tree->changeValue(convert16Bit(bigEndian, sDataPtr + skip, false, (frameHead.size - skip)));
+ }
+
+ }
+ else if (CheckBytes(frameHead.id, "PRIV", 4) && (frameHead.size >= 4)) {
+
+ // checking on the XMP packet
+ CaptureFileData(file, 0, frameHead.size); //NB: has side effect: sDataLen, sDataMax, sDataPtr
+ tree->changeValue(convert8Bit(sDataPtr, false, strlen((char*)sDataPtr)));
+ if (CheckBytes(sDataPtr, "XMP\x0", 4)) {
+ CaptureXMPF(file, (framePos + sizeof(frameHead) + 4), (frameHead.size - 4));
+ }
+
+ }
+ else if (CheckBytes(frameHead.id, "COMM", 4) || CheckBytes(frameHead.id, "USLT", 4)) {
+
+ const char * descrLabel = "ID3v2:COMM-descr";
+ if (CheckBytes(frameHead.id, "USLT", 4)) descrLabel = "ID3v2:USLT-descr";
+
+ CaptureFileData(file, 0, frameHead.size);
+ XMP_Uns8 * frameEnd2 = sDataPtr + frameHead.size;
+
+ XMP_Uns8 encoding = sDataPtr[0];
+ char * lang = (char*)(sDataPtr + 1);
+
+ tree->addComment("lang '%.3s'", lang);
+ bool bigEndian = PrintID3Encoding(encoding, (sDataPtr + 4));
+
+ if ((encoding == 0) || (encoding == 3)) {
+
+ XMP_Uns8 * descrPtr = sDataPtr + 4;
+ XMP_Uns8 * valuePtr = descrPtr;
+
+ while (*valuePtr != 0) ++valuePtr;
+ ++valuePtr;
+
+ size_t descrBytes = valuePtr - descrPtr - 1;
+ tree->changeValue(convert8Bit(valuePtr, false, frameEnd2 - valuePtr));
+ tree->setKeyValue(descrLabel, convert8Bit(descrPtr, false, descrBytes).c_str());
+
+ }
+ else if ((encoding == 1) || (encoding == 2)) {
+
+ XMP_Uns16 * descrPtr = (XMP_Uns16*)(sDataPtr + 4);
+ XMP_Uns16 * valuePtr = descrPtr;
+ while (*valuePtr != 0) ++valuePtr;
+ ++valuePtr;
+
+ size_t descrBytes = 2 * (valuePtr - descrPtr - 1);
+ size_t valueBytes = 2 * ((XMP_Uns16*)frameEnd2 - valuePtr);
+
+ tree->changeValue(convert16Bit(bigEndian, (XMP_Uns8*)valuePtr, false, valueBytes));
+ tree->setKeyValue(descrLabel, convert16Bit(bigEndian, (XMP_Uns8*)descrPtr, false, descrBytes).c_str());
+
+ }
+
+ }
+
+ else if (CheckBytes(frameHead.id, "APIC", 4)) {
+
+ ++iIterator;
+ unsigned int iOffset = 0;
+ CaptureFileData(file, 0, frameHead.size);
+
+ char encoding[2];
+ memset(encoding, 0x0, 2);
+ encoding[0] = sDataPtr[iOffset++];
+ tree->setKeyValue(fromArgs("ID3v2:APIC-encodingType_%d", iIterator), encoding);
+
+ char * mimeType = (char*)(sDataPtr + iOffset);
+ iOffset += strlen(mimeType) + 1; //1 is for null termination
+ tree->setKeyValue(fromArgs("ID3v2:APIC-mimeType_%d", iIterator), mimeType);
+
+ char pictureType[2];
+ memset(pictureType, 0x0, 2);
+ pictureType[0] = sDataPtr[iOffset++];
+ tree->setKeyValue(fromArgs("ID3v2:APIC-pictureType_%1d", iIterator), pictureType);
+
+ bool bigEndian = PrintID3Encoding(encoding[0], (sDataPtr + iOffset));
+ if (encoding[0] == 0x00) {
+
+ XMP_Uns8 * descrPtr = sDataPtr + iOffset;
+ XMP_Uns8 * valuePtr = descrPtr;
+
+ while (*valuePtr != 0) ++valuePtr;
+ ++valuePtr; //Null termination
+
+ size_t descrBytes = valuePtr - descrPtr;
+ tree->setKeyValue(fromArgs("ID3v2:APIC-descr_%d", iIterator), convert8Bit(descrPtr, false, descrBytes - 1).c_str());
+ iOffset += descrBytes;
+ }
+ else if (encoding[0] == 0x01) {
+
+ XMP_Uns16 * descrPtr = (XMP_Uns16*)(sDataPtr + iOffset);
+ XMP_Uns16 * valuePtr = descrPtr;
+
+ while (*valuePtr != 0) ++valuePtr;
+ ++valuePtr; //Null termination
+
+ size_t descrBytes = 2 * (valuePtr - descrPtr);
+ tree->setKeyValue(fromArgs("ID3v2:APIC-descr_%d", iIterator), convert16Bit(bigEndian, (XMP_Uns8*)(descrPtr + 1), false, descrBytes - 4).c_str());
+ iOffset += descrBytes;
+ }
+
+ XMP_Uns8 *picPtr = (sDataPtr + iOffset);
+ unsigned long size_PictureData = frameHead.size - iOffset;
+
+ char picDataSize[8];
+ memset(picDataSize, 0x0, 8);
+ sprintf(picDataSize, "%lu", size_PictureData);
+
+ std::string picData;
+ picData.assign((char*)picPtr, size_PictureData);
+
+ tree->setKeyValue(fromArgs("ID3v2:APIC-pictureData_%d", iIterator), picData);
+ tree->setKeyValue(fromArgs("ID3v2:APIC-pictureDataSize_%d", iIterator), picDataSize);
+ }
+
+ framePos += (sizeof(frameHead) + frameHead.size);
+
+ }
+
+ if (iIterator) {
+ char noOfAPICs[2];
+ memset(noOfAPICs, 0x0, 2);
+ sprintf(noOfAPICs, "%d", iIterator);
+ tree->setKeyValue("ID3v2:NoOfAPIC", noOfAPICs);
+ }
+
+ if (framePos < frameEnd) {
+ tree->setKeyValue("", "",
+ fromArgs("Padding assumed, offset %d (0x%X), size %d", framePos, framePos, (frameEnd - framePos)));
+ }
+
+} // DumpID3v23Frames
+
+ // =================================================================================================
+
+static void
+DumpMP3(LFA_FileRef file, XMP_Uns32 /*mp3Len*/)
+{
+ // ** We're ignoring the effects of the unsync flag, and not checking the CRC (if present).
+ assert(sizeof(ID3_Header) == 10);
+ assert(sizeof(ID3_v23_FrameHeader) == 10);
+
+ // Detect ID3v1 header:
+ if (LFA_Measure(file) > 128)
+ {
+ LFA_Seek(file, -128, SEEK_END);
+ XMP_Uns32 tagID = 0xFFFFFF00 & LFA_ReadUns32_BE(file);
+ if (tagID == 0x54414700) // must be "TAG"
+ {
+ // Dump ID3v1 header:
+ tree->pushNode("ID3v1");
+ Rewind(file, 1); // read one byte too many...
+
+ tree->digestString(file, "ID3v1:title", 30, false, true);
+ tree->digestString(file, "ID3v1:artist", 30, false, true);
+ tree->digestString(file, "ID3v1:album", 30, false, true);
+ tree->digestString(file, "ID3v1:year", 4, false, true);
+ tree->digestString(file, "ID3v1:comment", 30, false, true);
+ tree->digest(file, "ID3v1:genreNo", 0, 1);
+
+ // ID3v1.1 trackNo byte dance:
+ Rewind(file, 3);
+ LFA_Tell(file);
+ if (LFA_ReadUns8(file) == 0)
+ tree->digest(file, "ID3v1:trackNo", 0, 1);
+
+ tree->popNode();
+ }
+ }
+
+ // Dump ID3v2 header:
+ ID3_Header id3Head;
+ LFA_Seek(file, 0, SEEK_SET);
+ LFA_Read(file, &id3Head, sizeof(id3Head), true);
+
+ if (!CheckBytes(id3Head.id3, "ID3", 3)) {
+ tree->setKeyValue("No ID3v2 tag");
+ return;
+ }
+
+ XMP_Uns32 id3Len = GetSyncSafe32(id3Head.splitSize);
+ XMP_Uns32 framePos = sizeof(id3Head); // The offset of the next (first) ID3 frame.
+ XMP_Uns32 frameEnd = framePos + id3Len;
+
+ tree->pushNode("ID3v2.%d.%d, size %d, flags 0x%.2X", id3Head.vMajor, id3Head.vMinor, id3Len, id3Head.flags);
+
+ if (id3Head.flags != 0) {
+ tree->addComment("%s%s%s%s",
+ ((id3Head.flags & 0x80) ? ", unsync" : ""),
+ ((id3Head.flags & 0x40) ? ", extended header" : ""),
+ ((id3Head.flags & 0x20) ? ", experimental" : ""),
+ ((id3Head.flags & 0x10) ? ", has footer" : ""));
+ }
+
+ if ((id3Head.vMajor < 2) || (id3Head.vMajor > 4)) {
+ tree->addComment(" ** Unrecognized major version tree.");
+ tree->popNode();
+ return;
+ }
+
+ bool hasExtHeader = ((id3Head.flags & 0x40) != 0);
+
+ // Dump the extended header if present.
+ if (hasExtHeader) {
+ XMP_Uns32 extHeaderLen;
+
+ extHeaderLen = tree->digest32u(file);
+ extHeaderLen = GetID3Size(id3Head.vMajor, &extHeaderLen);
+ framePos += (4 + extHeaderLen);
+
+ switch (id3Head.vMajor) {
+
+ case 2: {
+ // #error "implement"
+ break;
+ }
+
+ case 3: {
+ XMP_Uns16 extHeaderFlags;
+ LFA_Read(file, &extHeaderFlags, 2, true);
+ extHeaderFlags = GetUns16BE(&extHeaderFlags);
+
+ XMP_Uns32 padLen;
+ LFA_Read(file, &padLen, 4, true);
+ padLen = GetUns32BE(&padLen);
+
+ frameEnd -= padLen;
+
+ tree->pushNode("Extended header MajorV3 size %d, flags 0x%.4X, pad size %d",
+ extHeaderLen, extHeaderFlags, padLen);
+
+ if (extHeaderFlags & 0x8000) {
+ XMP_Uns32 crc;
+ LFA_Read(file, &crc, 4, true);
+ crc = GetUns32BE(&crc);
+ tree->setKeyValue("CRC", fromArgs("0x%.8X", crc));
+ }
+ tree->popNode();
+ break;
+ }
+
+ case 4: {
+ XMP_Uns8 flagCount;
+ LFA_Read(file, &flagCount, 1, true);
+
+ tree->pushNode("Extended header MajorV4 size %d, flag count %d", extHeaderLen, flagCount);
+
+ for (size_t i = 0; i < flagCount; ++i) {
+ XMP_Uns8 flag;
+ LFA_Read(file, &flag, 1, true);
+ tree->setKeyValue(fromArgs("Flag %.2d", flag), fromArgs("0x%.2X", flag));
+ }
+ tree->popNode();
+ break;
+ }
+
+ default:
+ tree->addComment("unknown major version !");
+ break;
+
+ }
+
+ }
+
+ ////////////////////////////////////////////////////
+ // Dump the ID3 frames
+
+ if (id3Head.vMajor == 2) {
+ DumpID3v22Frames(file, id3Head.vMajor, framePos, frameEnd);
+ }
+ else {
+ DumpID3v23Frames(file, id3Head.vMajor, framePos, frameEnd);
+ }
+
+ if (sXMPPtr != 0) DumpXMP("ID3 'PRIV' \"XMP\" frame");
+
+ tree->popNode();
+} // DumpMP3
+
+ // =================================================================================================
+
+static void
+PacketScan(LFA_FileRef file, XMP_Int64 fileLen)
+{
+ try {
+
+ XMPScanner scanner(fileLen);
+ LFA_Seek(file, 0, SEEK_SET);
+
+ XMP_Uns8 buffer[64 * 1024];
+ XMP_Uns32 filePos, readLen;
+
+ for (filePos = 0; filePos < fileLen; filePos += readLen) {
+ readLen = LFA_Read(file, buffer, sizeof(buffer), false);
+ if (readLen == 0) throw std::logic_error("Empty read");
+ scanner.Scan(buffer, filePos, readLen);
+ }
+
+ size_t snipCount = scanner.GetSnipCount();
+ XMPScanner::SnipInfoVector snips(snipCount);
+ scanner.Report(snips);
+
+ size_t packetCount = 0;
+
+ for (size_t s = 0; s < snipCount; ++s) {
+ if (snips[s].fState == XMPScanner::eValidPacketSnip) {
+ ++packetCount;
+ CaptureXMPF(file, (XMP_Uns32)snips[s].fOffset, (XMP_Uns32)snips[s].fLength);
+ DumpXMP("packet scan");
+ }
+ }
+
+ if (packetCount == 0) tree->addComment(" No packets found");
+
+ }
+ catch (...) {
+
+ tree->addComment("** Scanner failure tree.");
+
+ }
+
+} // PacketScan
+
+ // =================================================================================================
+ // External Routines
+
+namespace DumpFile_NS {
+ // ! Xcode compiler warns about normal offsetof macro.
+#define SafeOffsetOf(type,field) ((size_t)(&(((type*)1000)->field)) - 1000)
+
+ //assure that packing is 100% tight
+ //see above:
+ // - #pragma pack (1)
+ // - SafeOffsetOf macro definition
+ //
+ // calling this at least once is an extremly good idea,
+ // because among other reasons, the #pragma pack directive
+ // is not ANSI-C thus things could go wrong on one platform or another...
+ //
+ // returns nothing, but asserts will be triggered if something is wrong.
+ static bool selfTestDone = false;
+ void selfTest() {
+ //only very first call at each runtime runs the selfTest (mostly verify about structPacking etc...)
+ if (DumpFile_NS::selfTestDone) return;
+
+ assert(sizeof(ASF_GUID) == 16);
+ assert(SafeOffsetOf(ASF_GUID, part1) == 0);
+
+ assert(SafeOffsetOf(ASF_GUID, part2) == 4);
+ assert(SafeOffsetOf(ASF_GUID, part3) == 6);
+ assert(SafeOffsetOf(ASF_GUID, part4) == 8);
+ assert(SafeOffsetOf(ASF_GUID, part5) == 10);
+
+ assert(sizeof(ASF_ObjHeader) == (16 + 8));
+ assert(SafeOffsetOf(ASF_ObjHeader, guid) == 0);
+ assert(SafeOffsetOf(ASF_ObjHeader, size) == 16);
+
+ assert(sizeof(ASF_FileProperties) == kASF_FilePropertiesSize);
+ assert(SafeOffsetOf(ASF_FileProperties, guid) == 0);
+ assert(SafeOffsetOf(ASF_FileProperties, size) == 16);
+ assert(SafeOffsetOf(ASF_FileProperties, fileID) == 24);
+ assert(SafeOffsetOf(ASF_FileProperties, fileSize) == 40);
+ assert(SafeOffsetOf(ASF_FileProperties, creationDate) == 48);
+ assert(SafeOffsetOf(ASF_FileProperties, dataPacketsCount) == 56);
+ assert(SafeOffsetOf(ASF_FileProperties, playDuration) == 64);
+ assert(SafeOffsetOf(ASF_FileProperties, sendDuration) == 72);
+ assert(SafeOffsetOf(ASF_FileProperties, preroll) == 80);
+ assert(SafeOffsetOf(ASF_FileProperties, flags) == 88);
+ assert(SafeOffsetOf(ASF_FileProperties, minDataPacketSize) == 92);
+ assert(SafeOffsetOf(ASF_FileProperties, maxDataPacketSize) == 96);
+ assert(SafeOffsetOf(ASF_FileProperties, maxBitrate) == 100);
+
+ assert(sizeof(ASF_ContentDescription) == kASF_ContentDescriptionSize);
+ assert(SafeOffsetOf(ASF_ContentDescription, guid) == 0);
+ assert(SafeOffsetOf(ASF_ContentDescription, size) == 16);
+ assert(SafeOffsetOf(ASF_ContentDescription, titleLen) == 24);
+ assert(SafeOffsetOf(ASF_ContentDescription, authorLen) == 26);
+ assert(SafeOffsetOf(ASF_ContentDescription, copyrightLen) == 28);
+ assert(SafeOffsetOf(ASF_ContentDescription, descriptionLen) == 30);
+ assert(SafeOffsetOf(ASF_ContentDescription, ratingLen) == 32);
+
+ assert(sizeof(InDesignMasterPage) == kINDD_PageSize);
+ assert(SafeOffsetOf(InDesignMasterPage, fGUID) == 0);
+ assert(SafeOffsetOf(InDesignMasterPage, fMagicBytes) == 16);
+ assert(SafeOffsetOf(InDesignMasterPage, fObjectStreamEndian) == 24);
+ assert(SafeOffsetOf(InDesignMasterPage, fIrrelevant1) == 25);
+ assert(SafeOffsetOf(InDesignMasterPage, fSequenceNumber) == 264);
+ assert(SafeOffsetOf(InDesignMasterPage, fIrrelevant2) == 272);
+ assert(SafeOffsetOf(InDesignMasterPage, fFilePages) == 280);
+ assert(SafeOffsetOf(InDesignMasterPage, fIrrelevant3) == 284);
+
+ assert(sizeof(InDesignContigObjMarker) == 32);
+ assert(SafeOffsetOf(InDesignContigObjMarker, fGUID) == 0);
+ assert(SafeOffsetOf(InDesignContigObjMarker, fObjectUID) == 16);
+ assert(SafeOffsetOf(InDesignContigObjMarker, fObjectClassID) == 20);
+ assert(SafeOffsetOf(InDesignContigObjMarker, fStreamLength) == 24);
+ assert(SafeOffsetOf(InDesignContigObjMarker, fChecksum) == 28);
+
+ selfTestDone = true;
+ } // selfTest
+
+} /*namespace DumpFile_NS*/
+
+ // -------------------------------------------------------------------------------------------------
+
+void DumpFile::Scan(std::string filename, TagTree &tagTree, bool resetTree)
+{
+ DumpFile_NS::selfTest(); //calls selftest (will happen only once per runtime, optimization done)
+
+ if (resetTree)
+ {
+ tagTree.reset();
+ }
+
+ tree = &tagTree; // static "global" helper to avoid looping throug 'tree' 24x7
+
+ // Read the first 4K of the file into a local buffer and determine the file format.
+ // ! We're using ANSI C calls that don't handle files over 2GB.
+ // ! Should switch to copies of the "LFA" routines used inside XMP.
+
+ LFA_FileRef fileRef = LFA_Open(filename.c_str(), 'r');
+
+ assertMsg(std::string("can't open ") + filename, fileRef != 0);
+
+ LFA_Seek(fileRef, 0, SEEK_END);
+ XMP_Int64 fileLen = LFA_Tell(fileRef);
+
+ XMP_Uns8 first4K[4096];
+
+ LFA_Seek(fileRef, 0, SEEK_SET);
+ LFA_Read(fileRef, first4K, 4096, false);
+
+ LFA_Seek(fileRef, 0, SEEK_SET); //rewinds
+ // (remains rewinded behind CheckFileDFormat, since that call does not get the fileRef handle)
+
+ XMP_FileFormat format = CheckFileFormat(filename.c_str(), first4K, fileLen);
+
+ if (sXMPPtr != 0) free(sXMPPtr);
+ sXMPPtr = 0;
+ sXMPMax = 0;
+ sXMPLen = 0;
+ sXMPPos = 0;
+
+ //TODO refactor-out
+ XMP_Uns8 * fileContent = 0; // *** Hack for old file-in-RAM code.
+
+ if (format == kXMP_JPEGFile) {
+
+ tagTree.pushNode("Dumping JPEG file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ {
+ fileContent = (XMP_Uns8*)malloc(fileLen);
+ LFA_Seek(fileRef, 0, SEEK_SET);
+ LFA_Read(fileRef, fileContent, fileLen, true);
+ DumpJPEG(fileContent, fileLen);
+ }
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_PhotoshopFile) {
+
+ tagTree.pushNode("Dumping Photoshop file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ {
+ fileContent = (XMP_Uns8*)malloc(fileLen);
+ LFA_Seek(fileRef, 0, SEEK_SET);
+ LFA_Read(fileRef, fileContent, fileLen, true);
+ DumpPhotoshop(fileContent, fileLen);
+ }
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_TIFFFile) {
+
+ tagTree.pushNode("Dumping TIFF file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ {
+ fileContent = (XMP_Uns8*)malloc(fileLen);
+ LFA_Seek(fileRef, 0, SEEK_SET);
+ LFA_Read(fileRef, fileContent, fileLen, true);
+ DumpTIFF(fileContent, fileLen, 0, "TIFF file", "");
+ }
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_WMAVFile) {
+
+ tagTree.pushNode("Dumping ASF (WMA/WMV) file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpASF(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_AVIFile) {
+
+ tagTree.pushNode("Dumping AVI file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpRIFF(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_WAVFile) {
+
+ tagTree.pushNode("Dumping WAV file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpRIFF(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_AIFFFile) {
+
+ tagTree.pushNode("Dumping AIFF file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpAIFF(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_MPEG4File || format == kXMP_MOVFile || format == kXMP_JPEG2KFile || format == kXMP_HEIFFile) {
+ // all ISO formats ( MPEG4, MOV, JPEG2000,HEIF) handled jointly,
+ // - no longer relying on any advance "isQT" flagging
+ tagTree.pushNode("ISO file");
+ Log::info("size: %d", fileLen);
+ // tagTree.addComment ( "size %I64d (0x%I64X)", fileLen, fileLen );
+ tagTree.addComment("size %lld (0x%llX)", fileLen, fileLen);
+ ISOMetaKeys.clear();
+ DumpISO(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_PNGFile) {
+
+ tagTree.pushNode("Dumping PNG file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpPNG(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_InDesignFile) {
+
+ tagTree.pushNode("Dumping InDesign file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpInDesign(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_SWFFile) {
+
+ tagTree.pushNode("Dumping SWF file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpSWF(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_FLVFile) {
+
+ tagTree.pushNode("Dumping FLV file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpFLV(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_MP3File) {
+
+ tagTree.pushNode("Dumping MP3 file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpMP3(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_UCFFile) {
+
+ tagTree.pushNode("Dumping UCF (Universal Container Format) file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpUCF(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_MPEGFile) {
+
+ tagTree.comment("** Recognized MPEG-2 file type, but this is a pure sidecar solution. No legacy dump available at this time.");
+
+ }
+ else if (format == kXMP_PostScriptFile) {
+
+ tagTree.pushNode("Dumping PostScript file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpPS(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else if (format == kXMP_SVGFile) {
+
+ tagTree.pushNode("Dumping SVG file");
+ tagTree.addComment("size %lld (0x%llx)", fileLen, fileLen);
+ DumpSVG(fileRef, fileLen);
+ tagTree.popNode();
+ }
+ else if (format == kXMP_UnknownFile) {
+
+ tagTree.pushNode("Unknown format. packet scanning, size %d (0x%X)", fileLen, fileLen);
+ PacketScan(fileRef, fileLen);
+ tagTree.popNode();
+
+ }
+ else {
+ tagTree.comment("** Recognized file type, '%.4s', but no smart dumper for it.", &format);
+ }
+
+ if (fileContent != 0) free(fileContent);
+ LFA_Close(fileRef);
+
+} // DumpFile
+
+
+void DumpFile::dumpFile(std::string filename)
+{
+ TagTree localTree;
+ DumpFile::Scan(filename, localTree); // (important test in itself for validity)
+ localTree.dumpTree();
+}
+
diff --git a/samples/source/common/DumpFile.h b/samples/source/common/DumpFile.h
new file mode 100644
index 0000000..3324dc5
--- /dev/null
+++ b/samples/source/common/DumpFile.h
@@ -0,0 +1,25 @@
+// =================================================================================================
+// Copyright 2002-2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+
+#ifndef XMPQE_DUMPFILE_H
+#define XMPQE_DUMPFILE_H
+
+#include "samples/source/common/TagTree.h"
+#define IsNumeric( ch ) (ch >='0' && ch<='9' )
+
+class DumpFile {
+public:
+ static void Scan( std::string filename, TagTree &tagTree, bool resetTree = true );
+
+ /* dumps file to output, no strings attached Log::info() */
+ static void dumpFile( std::string filename );
+};
+
+#endif
+
diff --git a/samples/source/common/LargeFileAccess.cpp b/samples/source/common/LargeFileAccess.cpp
new file mode 100644
index 0000000..4940721
--- /dev/null
+++ b/samples/source/common/LargeFileAccess.cpp
@@ -0,0 +1,908 @@
+// =================================================================================================
+// ADOBE SYSTEMS INCORPORATED
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include <source/Host_IO.hpp>
+#include "samples/source/common/LargeFileAccess.hpp"
+#if WIN_UNIVERSAL_ENV
+#include <strsafe.h>
+#endif
+
+void LFA_Throw ( const char* msg, int id )
+{
+ switch(id)
+ {
+ case kLFAErr_InternalFailure:
+ throw XMP_Error ( kXMPErr_InternalFailure, msg );
+ break;
+ case kLFAErr_ExternalFailure:
+ throw XMP_Error ( kXMPErr_ExternalFailure, msg );
+ break;
+ case kLFAErr_UserAbort:
+ throw XMP_Error ( kXMPErr_UserAbort, msg );
+ break;
+ default:
+ throw XMP_Error ( kXMPErr_UnknownException, msg );
+ break;
+ }
+}
+
+// =================================================================================================
+// LFA implementations for Macintosh
+// =================================
+
+#if XMP_MacBuild
+
+ static bool FileExists ( const char * filePath )
+ {
+ struct stat info;
+ int err = stat ( filePath, &info );
+ return (err == 0);
+ }
+
+ // ---------------------------------------------------------------------------------------------
+
+ // ! Can't use Apple's 64 bit POSIX functions because frigging MSL has type clashes.
+ // *** Revisit now that we've switched to Xcode.
+
+ LFA_FileRef LFA_Open ( const char * filePath, char mode )
+ {
+ if ( (mode != 'r') && (mode != 'w') )
+ LFA_Throw ( "LFA_Open: invalid mode", kLFAErr_ExternalFailure );;
+
+ FSRef fileRef;
+ SInt8 perm = ( (mode == 'r') ? fsRdPerm : fsRdWrPerm );
+ HFSUniStr255 dataForkName;
+#if __LP64__
+ FSIORefNum refNum;
+#else
+ SInt16 refNum;
+#endif
+
+ OSErr err = FSGetDataForkName ( &dataForkName );
+ if ( err != noErr ) LFA_Throw ( "LFA_Open: FSGetDataForkName failure", kLFAErr_ExternalFailure );
+
+ err = FSPathMakeRef ( (XMP_Uns8*)filePath, &fileRef, 0 );
+ if ( err != noErr ) LFA_Throw ( "LFA_Open: FSPathMakeRef failure", kLFAErr_ExternalFailure );
+
+ err = FSOpenFork ( &fileRef, dataForkName.length, dataForkName.unicode, perm, &refNum );
+ if ( err != noErr ) LFA_Throw ( "LFA_Open: FSOpenFork failure", kLFAErr_ExternalFailure );
+
+ return (LFA_FileRef)refNum;
+
+ } // LFA_Open
+
+ // ---------------------------------------------------------------------------------------------
+
+ LFA_FileRef LFA_Create ( const char * filePath )
+ {
+ // *** Hack: Use fopen to avoid parent/child name separation needed by FSCreateFileUnicode.
+
+ if ( FileExists ( filePath ) ) {
+ LFA_Throw ( "LFA_Create: file already exists", kLFAErr_ExternalFailure );
+ }
+
+ FILE * temp = fopen ( filePath, "w" );
+ if ( temp == 0 ) LFA_Throw ( "LFA_Create: fopen failure", kLFAErr_ExternalFailure );
+ fclose ( temp );
+
+ return LFA_Open ( filePath, 'w' );
+
+ } // LFA_Create
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Delete ( const char * filePath )
+ {
+ int err = remove ( filePath ); // *** Better to use an FS function.
+ if ( err != 0 ) LFA_Throw ( "LFA_Delete: remove failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Delete
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Rename ( const char * oldName, const char * newName )
+ {
+ int err = rename ( oldName, newName ); // *** Better to use an FS function.
+ if ( err != 0 ) LFA_Throw ( "LFA_Rename: rename failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Rename
+
+ // ---------------------------------------------------------------------------------------------
+
+ LFA_FileRef LFA_OpenRsrc ( const char * filePath, char mode )
+ {
+ if ( (mode != 'r') && (mode != 'w') )
+ LFA_Throw ( "LFA_OpenRsrc: invalid mode", kLFAErr_ExternalFailure );;
+
+ FSRef fileRef;
+ SInt8 perm = ( (mode == 'r') ? fsRdPerm : fsRdWrPerm );
+ HFSUniStr255 rsrcForkName;
+#if __LP64__
+ FSIORefNum refNum;
+#else
+ SInt16 refNum;
+#endif
+
+ OSErr err = FSGetResourceForkName ( &rsrcForkName );
+ if ( err != noErr ) LFA_Throw ( "LFA_OpenRsrc: FSGetResourceForkName failure", kLFAErr_ExternalFailure );
+
+ err = FSPathMakeRef ( (XMP_Uns8*)filePath, &fileRef, 0 );
+ if ( err != noErr ) LFA_Throw ( "LFA_OpenRsrc: FSPathMakeRef failure", kLFAErr_ExternalFailure );
+
+ err = FSOpenFork ( &fileRef, rsrcForkName.length, rsrcForkName.unicode, perm, &refNum );
+ if ( err != noErr ) LFA_Throw ( "LFA_OpenRsrc: FSOpenFork failure", kLFAErr_ExternalFailure );
+
+ return (LFA_FileRef)refNum;
+
+ } // LFA_OpenRsrc
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Close ( LFA_FileRef file )
+ {
+ if ( file == 0 ) return; // Can happen if LFA_Open throws an exception.
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+
+ OSErr err = FSCloseFork ( refNum );
+ if ( err != noErr ) LFA_Throw ( "LFA_Close: FSCloseFork failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Close
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int64 LFA_Seek ( LFA_FileRef file, XMP_Int64 offset, int mode, bool * okPtr )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+
+ UInt16 posMode = 0;
+ switch ( mode ) {
+ case SEEK_SET :
+ posMode = fsFromStart;
+ break;
+ case SEEK_CUR :
+ posMode = fsFromMark;
+ break;
+ case SEEK_END :
+ posMode = fsFromLEOF;
+ break;
+ default :
+ LFA_Throw ( "LFA_Seek: Invalid seek mode", kLFAErr_InternalFailure );
+ break;
+ }
+
+ OSErr err;
+ XMP_Int64 newPos = 0;
+
+ err = FSSetForkPosition ( refNum, posMode, offset );
+
+ if ( err == eofErr ) {
+ // FSSetForkPosition does not implicitly grow the file. Grow then seek to the new EOF.
+ err = FSSetForkSize ( refNum, posMode, offset );
+ if ( err == noErr ) err = FSSetForkPosition ( refNum, fsFromLEOF, 0 );
+ }
+
+ if ( err == noErr ) err = FSGetForkPosition ( refNum, &newPos );
+
+ if ( okPtr != 0 ) {
+ *okPtr = (err == noErr);
+ } else {
+ if ( err != noErr ) LFA_Throw ( "LFA_Seek: FSSetForkPosition failure", kLFAErr_ExternalFailure );
+ }
+
+ return newPos;
+
+ } // LFA_Seek
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int32 LFA_Read ( LFA_FileRef file, void * buffer, XMP_Int32 bytes, bool requireAll )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+ ByteCount bytesRead;
+
+ OSErr err = FSReadFork ( refNum, fsAtMark, 0, bytes, buffer, &bytesRead );
+ if ( ((err != noErr) && (err != eofErr)) || (requireAll && (bytesRead != (ByteCount)bytes)) ) {
+ // ! FSReadFork returns eofErr for a normal encounter with the end of file.
+ LFA_Throw ( "LFA_Read: FSReadFork failure", kLFAErr_ExternalFailure );
+ }
+
+ return bytesRead;
+
+ } // LFA_Read
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Write ( LFA_FileRef file, const void * buffer, XMP_Int32 bytes )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+ ByteCount bytesWritten;
+
+ OSErr err = FSWriteFork ( refNum, fsAtMark, 0, bytes, buffer, &bytesWritten );
+ if ( (err != noErr) | (bytesWritten != (ByteCount)bytes) ) LFA_Throw ( "LFA_Write: FSWriteFork failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Write
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Flush ( LFA_FileRef file )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+
+ OSErr err = FSFlushFork ( refNum );
+ if ( err != noErr ) LFA_Throw ( "LFA_Flush: FSFlushFork failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Flush
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int64 LFA_Measure ( LFA_FileRef file )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+ XMP_Int64 length;
+
+ OSErr err = FSGetForkSize ( refNum, &length );
+ if ( err != noErr ) LFA_Throw ( "LFA_Measure: FSSetForkSize failure", kLFAErr_ExternalFailure );
+
+ return length;
+
+ } // LFA_Measure
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Extend ( LFA_FileRef file, XMP_Int64 length )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+
+ OSErr err = FSSetForkSize ( refNum, fsFromStart, length );
+ if ( err != noErr ) LFA_Throw ( "LFA_Extend: FSSetForkSize failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Extend
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Truncate ( LFA_FileRef file, XMP_Int64 length )
+ {
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+
+ OSErr err = FSSetForkSize ( refNum, fsFromStart, length );
+ if ( err != noErr ) LFA_Throw ( "LFA_Truncate: FSSetForkSize failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Truncate
+
+ // ---------------------------------------------------------------------------------------------
+
+#endif // XMP_MacBuild
+
+// =================================================================================================
+// LFA implementations for Windows
+// ===============================
+
+#if XMP_WinBuild
+ // ---------------------------------------------------------------------------------------------
+
+ LFA_FileRef LFA_Open ( const char * filePath, char mode )
+ {
+ if ((mode != 'r') && (mode != 'w'))
+ LFA_Throw("LFA_Open: invalid mode", kLFAErr_ExternalFailure);;
+
+ DWORD access = GENERIC_READ; // Assume read mode.
+ DWORD share = FILE_SHARE_READ;
+
+ if (mode == 'w') {
+ access |= GENERIC_WRITE;
+ share = 0;
+ }
+
+ std::string wideName;
+ const size_t utf8Len = strlen(filePath);
+ const size_t maxLen = 2 * (utf8Len + 1);
+
+ wideName.reserve(maxLen);
+ wideName.assign(maxLen, ' ');
+ int wideLen = MultiByteToWideChar(CP_UTF8, 0, filePath, -1, (LPWSTR)wideName.data(), (int)maxLen);
+ if (wideLen == 0) LFA_Throw("LFA_Open: MultiByteToWideChar failure", kLFAErr_ExternalFailure);
+
+ HANDLE fileHandle;// = INVALID_HANDLE_VALUE;
+
+#ifdef WIN_UNIVERSAL_ENV
+ CREATEFILE2_EXTENDED_PARAMETERS params;
+ params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
+ params.dwFileAttributes = (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS);
+ params.lpSecurityAttributes = 0;
+ params.hTemplateFile = 0;
+ params.dwFileFlags = 0;
+ params.dwSecurityQosFlags = 0;
+ fileHandle = CreateFile2((LPCWSTR)wideName.data(), access, share, OPEN_EXISTING, &params);
+#else
+ fileHandle = CreateFileW((LPCWSTR)wideName.data(), access, share, 0, OPEN_EXISTING,
+ (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS), 0);
+#endif //WIN_UNIVERSAL_ENV
+ if (fileHandle == INVALID_HANDLE_VALUE)
+ LFA_Throw("LFA_Open: CreateFileW failure", kLFAErr_ExternalFailure);
+
+ return (LFA_FileRef)fileHandle;
+
+
+ } // LFA_Open
+
+ // ---------------------------------------------------------------------------------------------
+
+ LFA_FileRef LFA_Create(const char * filePath)
+ {
+ std::string wideName;
+ const size_t utf8Len = strlen(filePath);
+ const size_t maxLen = 2 * (utf8Len + 1);
+
+ wideName.reserve(maxLen);
+ wideName.assign(maxLen, ' ');
+ int wideLen = MultiByteToWideChar(CP_UTF8, 0, filePath, -1, (LPWSTR)wideName.data(), (int)maxLen);
+ if (wideLen == 0) LFA_Throw("LFA_Create: MultiByteToWideChar failure", kLFAErr_ExternalFailure);
+
+ HANDLE fileHandle;
+#ifdef WIN_UNIVERSAL_ENV
+ CREATEFILE2_EXTENDED_PARAMETERS params;
+ params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
+ params.dwFileAttributes = (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS);
+ params.lpSecurityAttributes = 0;
+ params.hTemplateFile = 0;
+ params.dwFileFlags = 0;
+ params.dwSecurityQosFlags = 0;
+ fileHandle = CreateFile2((LPCWSTR)wideName.data(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, &params);
+#else
+ fileHandle = CreateFileW((LPCWSTR)wideName.data(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
+ (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS), 0);
+#endif //WIN_UNIVERSAL_ENV
+
+ if (fileHandle != INVALID_HANDLE_VALUE) {
+ CloseHandle(fileHandle);
+ LFA_Throw("LFA_Create: file already exists", kLFAErr_ExternalFailure);
+ }
+#ifdef WIN_UNIVERSAL_ENV
+ fileHandle = CreateFile2((LPCWSTR)wideName.data(), (GENERIC_READ | GENERIC_WRITE), 0, CREATE_ALWAYS, &params);
+#else
+ fileHandle = CreateFileW((LPCWSTR)wideName.data(), (GENERIC_READ | GENERIC_WRITE), 0, 0, CREATE_ALWAYS,
+ (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS), 0);
+#endif //WIN_UNIVERSAL_ENV
+
+ if (fileHandle == INVALID_HANDLE_VALUE) LFA_Throw("LFA_Create: CreateFileW failure", kLFAErr_ExternalFailure);
+
+ return (LFA_FileRef)fileHandle;
+
+ } // LFA_Create
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Delete ( const char * filePath )
+ {
+ std::string wideName;
+ const size_t utf8Len = strlen(filePath);
+ const size_t maxLen = 2 * (utf8Len+1);
+
+ wideName.reserve ( maxLen );
+ wideName.assign ( maxLen, ' ' );
+ int wideLen = MultiByteToWideChar ( CP_UTF8, 0, filePath, -1, (LPWSTR)wideName.data(), (int)maxLen );
+ if ( wideLen == 0 ) LFA_Throw ( "LFA_Delete: MultiByteToWideChar failure", kLFAErr_ExternalFailure );
+
+ BOOL ok = DeleteFileW ( (LPCWSTR)wideName.data() );
+ if ( ! ok ) LFA_Throw ( "LFA_Delete: DeleteFileW failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Delete
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Rename(const char * oldName, const char * newName)
+ {
+ std::string wideOldName, wideNewName;
+ size_t utf8Len = strlen(oldName);
+ if (utf8Len < strlen(newName)) utf8Len = strlen(newName);
+ const size_t maxLen = 2 * (utf8Len + 1);
+ int wideLen;
+
+ wideOldName.reserve(maxLen);
+ wideOldName.assign(maxLen, ' ');
+ wideLen = MultiByteToWideChar(CP_UTF8, 0, oldName, -1, (LPWSTR)wideOldName.data(), (int)maxLen);
+ if (wideLen == 0) LFA_Throw("LFA_Rename: MultiByteToWideChar failure", kLFAErr_ExternalFailure);
+
+ wideNewName.reserve(maxLen);
+ wideNewName.assign(maxLen, ' ');
+ wideLen = MultiByteToWideChar(CP_UTF8, 0, newName, -1, (LPWSTR)wideNewName.data(), (int)maxLen);
+ if (wideLen == 0) LFA_Throw("LFA_Rename: MultiByteToWideChar failure", kLFAErr_ExternalFailure);
+#ifdef WIN_UNIVERSAL_ENV
+ BOOL ok = ReplaceFile((LPCWSTR)wideOldName.data(), (LPCWSTR)wideNewName.data(), NULL, REPLACEFILE_IGNORE_MERGE_ERRORS, 0, 0);
+#else
+ BOOL ok = MoveFileW((LPCWSTR)wideOldName.data(), (LPCWSTR)wideNewName.data());
+#endif //WIN_UNIVERSAL_ENV
+ if (!ok) LFA_Throw("LFA_Rename: MoveFileW failure", kLFAErr_ExternalFailure);
+
+ } // LFA_Rename
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Close ( LFA_FileRef file )
+ {
+ if ( file == 0 ) return; // Can happen if LFA_Open throws an exception.
+ HANDLE fileHandle = (HANDLE)file;
+
+ BOOL ok = CloseHandle ( fileHandle );
+ if ( ! ok ) LFA_Throw ( "LFA_Close: CloseHandle failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Close
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int64 LFA_Seek ( LFA_FileRef file, XMP_Int64 offset, int mode, bool * okPtr )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+
+ DWORD method;
+ switch ( mode ) {
+ case SEEK_SET :
+ method = FILE_BEGIN;
+ break;
+ case SEEK_CUR :
+ method = FILE_CURRENT;
+ break;
+ case SEEK_END :
+ method = FILE_END;
+ break;
+ default :
+ LFA_Throw ( "Invalid seek mode", kLFAErr_InternalFailure );
+ break;
+ }
+
+ LARGE_INTEGER seekOffset, newPos;
+ seekOffset.QuadPart = offset;
+
+ BOOL ok = SetFilePointerEx ( fileHandle, seekOffset, &newPos, method );
+ if ( okPtr != 0 ) {
+ *okPtr = ( ok != 0 ); //convert int(disguised as BOOL) to bool, avoiding conversion warning
+ } else {
+ if ( ! ok ) LFA_Throw ( "LFA_Seek: SetFilePointerEx failure", kLFAErr_ExternalFailure );
+ }
+
+ return newPos.QuadPart;
+
+ } // LFA_Seek
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int32 LFA_Read ( LFA_FileRef file, void * buffer, XMP_Int32 bytes, bool requireAll )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+ DWORD bytesRead;
+
+ BOOL ok = ReadFile ( fileHandle, buffer, bytes, &bytesRead, 0 );
+ if ( (! ok) || (requireAll && (bytesRead != bytes)) ) LFA_Throw ( "LFA_Read: ReadFile failure", kLFAErr_ExternalFailure );
+
+ return bytesRead;
+
+ } // LFA_Read
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Write ( LFA_FileRef file, const void * buffer, XMP_Int32 bytes )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+ DWORD bytesWritten;
+
+ BOOL ok = WriteFile ( fileHandle, buffer, bytes, &bytesWritten, 0 );
+ if ( (! ok) || (bytesWritten != bytes) ) LFA_Throw ( "LFA_Write: WriteFile failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Write
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Flush ( LFA_FileRef file )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+
+ BOOL ok = FlushFileBuffers ( fileHandle );
+ if ( ! ok ) LFA_Throw ( "LFA_Flush: FlushFileBuffers failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Flush
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int64 LFA_Measure ( LFA_FileRef file )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+ LARGE_INTEGER length;
+
+ BOOL ok = GetFileSizeEx ( fileHandle, &length );
+ if ( ! ok ) LFA_Throw ( "LFA_Measure: GetFileSizeEx failure", kLFAErr_ExternalFailure );
+
+ return length.QuadPart;
+
+ } // LFA_Measure
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Extend ( LFA_FileRef file, XMP_Int64 length )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+
+ LARGE_INTEGER winLength;
+ winLength.QuadPart = length;
+
+ BOOL ok = SetFilePointerEx ( fileHandle, winLength, 0, FILE_BEGIN );
+ if ( ! ok ) LFA_Throw ( "LFA_Extend: SetFilePointerEx failure", kLFAErr_ExternalFailure );
+ ok = SetEndOfFile ( fileHandle );
+ if ( ! ok ) LFA_Throw ( "LFA_Extend: SetEndOfFile failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Extend
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Truncate ( LFA_FileRef file, XMP_Int64 length )
+ {
+ HANDLE fileHandle = (HANDLE)file;
+
+ LARGE_INTEGER winLength;
+ winLength.QuadPart = length;
+
+ BOOL ok = SetFilePointerEx ( fileHandle, winLength, 0, FILE_BEGIN );
+ if ( ! ok ) LFA_Throw ( "LFA_Truncate: SetFilePointerEx failure", kLFAErr_ExternalFailure );
+ ok = SetEndOfFile ( fileHandle );
+ if ( ! ok ) LFA_Throw ( "LFA_Truncate: SetEndOfFile failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Truncate
+
+ // ---------------------------------------------------------------------------------------------
+
+#endif // XMP_WinBuild
+
+// =================================================================================================
+// LFA implementations for POSIX
+// =============================
+
+#if XMP_UNIXBuild || XMP_iOSBuild || XMP_AndroidBuild
+
+ // ---------------------------------------------------------------------------------------------
+
+ // Make sure off_t is 64 bits and signed.
+ // Due to bug in NDK r12b size of off_t at 32 bit systems is 32 bit despite giving _FILE_OFFSET_BITS=64 flag. So only for Android off64_t is used
+ static char check_off_t_size [ (sizeof(Host_IO::XMP_off_t) == 8) ? 1 : -1 ];
+
+ // *** No std::numeric_limits? static char check_off_t_sign [ std::numeric_limits<off_t>::is_signed ? -1 : 1 ];
+
+ static bool FileExists ( const char * filePath )
+ {
+ struct stat info;
+ int err = stat ( filePath, &info );
+ return (err == 0);
+ }
+
+ // ---------------------------------------------------------------------------------------------
+
+ LFA_FileRef LFA_Open ( const char * filePath, char mode )
+ {
+ if ( (mode != 'r') && (mode != 'w') )
+ LFA_Throw ( "LFA_Open: invalid mode", kLFAErr_ExternalFailure );;
+
+ int flags = ((mode == 'r') ? O_RDONLY : O_RDWR); // *** Include O_EXLOCK?
+
+
+
+ int descr = open ( filePath, flags, ( S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP ) );
+ if ( descr == -1 )
+ LFA_Throw ( "LFA_Open: open failure", kLFAErr_ExternalFailure );
+
+ struct stat info;
+ if ( fstat(descr,&info) == -1 )
+ LFA_Throw( "LFA_Open: fstat failed.", kLFAErr_ExternalFailure );
+
+ // troublesome issue:
+ // a root user might be able to open a write-protected file w/o complaint
+ // although we should (to stay in sync with Mac/Win behaviour)
+ // reject write access (i.e. OpenForUpdate) to write-protected files:
+ if ( (mode == 'w') && ( 0 == (info.st_mode & S_IWUSR) ))
+ LFA_Throw( "LFA_Open:file is write proected", kLFAErr_ExternalFailure );
+
+ return (LFA_FileRef)descr;
+
+ } // LFA_Open
+
+ // ---------------------------------------------------------------------------------------------
+
+ LFA_FileRef LFA_Create ( const char * filePath )
+ {
+
+ if ( FileExists ( filePath ) ) {
+ LFA_Throw ( "LFA_Create: file already exists", kLFAErr_ExternalFailure );
+ }
+
+ mode_t mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ int descr = open ( filePath, (O_CREAT | O_EXCL | O_RDWR), mode ); // *** Include O_EXLOCK?
+ if ( descr == -1 ) LFA_Throw ( "LFA_Create: open failure", kLFAErr_ExternalFailure );
+
+ return (LFA_FileRef)descr;
+
+ } // LFA_Create
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Delete ( const char * filePath )
+ {
+ int err = unlink ( filePath );
+ if ( err != 0 ) LFA_Throw ( "LFA_Delete: unlink failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Delete
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Rename ( const char * oldName, const char * newName )
+ {
+ int err = rename ( oldName, newName ); // *** POSIX rename clobbers existing destination!
+ if ( err != 0 ) LFA_Throw ( "LFA_Rename: rename failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Rename
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Close ( LFA_FileRef file )
+ {
+ if ( file == 0 ) return; // Can happen if LFA_Open throws an exception.
+ int descr = (int)(size_t)file;
+
+ int err = close ( descr );
+ if ( err != 0 ) LFA_Throw ( "LFA_Close: close failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Close
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int64 LFA_Seek ( LFA_FileRef file, XMP_Int64 offset, int mode, bool * okPtr )
+ {
+ int descr = (int)(size_t)file;
+
+ Host_IO::XMP_off_t newPos = lseek ( descr, offset, mode );
+ if ( okPtr != 0 ) {
+ *okPtr = (newPos != -1);
+ } else {
+ if ( newPos == -1 ) LFA_Throw ( "LFA_Seek: lseek failure", kLFAErr_ExternalFailure );
+ }
+
+ return newPos;
+
+ } // LFA_Seek
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int32 LFA_Read ( LFA_FileRef file, void * buffer, XMP_Int32 bytes, bool requireAll )
+ {
+ int descr = (int)(size_t)file;
+
+ ssize_t bytesRead = read ( descr, buffer, bytes );
+ if ( (bytesRead == -1) || (requireAll && (bytesRead != bytes)) ) LFA_Throw ( "LFA_Read: read failure", kLFAErr_ExternalFailure );
+
+ return bytesRead;
+
+ } // LFA_Read
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Write ( LFA_FileRef file, const void * buffer, XMP_Int32 bytes )
+ {
+ int descr = (int)(size_t)file;
+
+ ssize_t bytesWritten = write ( descr, buffer, bytes );
+ if ( bytesWritten != bytes ) LFA_Throw ( "LFA_Write: write failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Write
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Flush ( LFA_FileRef file )
+ {
+ int descr = (int)(size_t)file;
+
+ int err = fsync ( descr );
+ if ( err != 0 ) LFA_Throw ( "LFA_Flush: fsync failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Flush
+
+ // ---------------------------------------------------------------------------------------------
+
+ XMP_Int64 LFA_Measure ( LFA_FileRef file )
+ {
+ int descr = (int)(size_t)file;
+
+ Host_IO::XMP_off_t currPos = lseek ( descr, 0, SEEK_CUR );
+ Host_IO::XMP_off_t length = lseek ( descr, 0, SEEK_END );
+ if ( (currPos == -1) || (length == -1) ) LFA_Throw ( "LFA_Measure: lseek failure", kLFAErr_ExternalFailure );
+ (void) lseek ( descr, currPos, SEEK_SET );
+
+ return length;
+
+ } // LFA_Measure
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Extend ( LFA_FileRef file, XMP_Int64 length )
+ {
+ int descr = (int)(size_t)file;
+
+ int err = ftruncate ( descr, length );
+ if ( err != 0 ) LFA_Throw ( "LFA_Extend: ftruncate failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Extend
+
+ // ---------------------------------------------------------------------------------------------
+
+ void LFA_Truncate ( LFA_FileRef file, XMP_Int64 length )
+ {
+ int descr = (int)(size_t)file;
+
+ int err = ftruncate ( descr, length );
+ if ( err != 0 ) LFA_Throw ( "LFA_Truncate: ftruncate failure", kLFAErr_ExternalFailure );
+
+ } // LFA_Truncate
+
+ // ---------------------------------------------------------------------------------------------
+
+#endif // XMP_UNIXBuild
+
+// =================================================================================================
+
+/* note! function does not rewind (LFA_Seek)) */
+void LFA_Copy ( LFA_FileRef sourceFile, LFA_FileRef destFile, XMP_Int64 length,
+ XMP_AbortProc abortProc /* = 0 */, void * abortArg /* = 0 */ )
+{
+ enum { kBufferLen = 64*1024 };
+ XMP_Uns8 buffer [kBufferLen];
+
+ const bool checkAbort = (abortProc != 0);
+
+ while ( length > 0 ) {
+
+ if ( checkAbort && abortProc(abortArg) ) {
+ LFA_Throw ( "LFA_Copy - User abort", kLFAErr_UserAbort );
+ }
+
+ XMP_Int32 ioCount = kBufferLen;
+ if ( length < kBufferLen ) ioCount = (XMP_Int32)length;
+
+ LFA_Read ( sourceFile, buffer, ioCount, kLFA_RequireAll );
+ LFA_Write ( destFile, buffer, ioCount );
+ length -= ioCount;
+
+ }
+
+} // LFA_Copy
+
+// =================================================================================================
+
+// allows to move data within a file (just pass in the same file handle as srcFile and dstFile)
+// shadow effects (stumbling over just-written data) are avoided.
+//
+// * however can also be used to move data between two files *
+// (having both option is handy for flexible use in update()/re-write() handler routines)
+
+void LFA_Move ( LFA_FileRef srcFile, XMP_Int64 srcOffset,
+ LFA_FileRef dstFile, XMP_Int64 dstOffset,
+ XMP_Int64 length, XMP_AbortProc abortProc /* = 0 */, void * abortArg /* = 0 */ )
+{
+ enum { kBufferLen = 64*1024 };
+ XMP_Uns8 buffer [kBufferLen];
+
+ const bool checkAbort = (abortProc != 0);
+
+ if ( srcOffset > dstOffset ) { // avoiding shadow effects
+
+ // move down -> shift lowest packet first !
+
+ while ( length > 0 ) {
+
+ if ( checkAbort && abortProc(abortArg) ) LFA_Throw ( "LFA_Move - User abort", kLFAErr_UserAbort );
+ XMP_Int32 ioCount = kBufferLen;
+ if ( length < kBufferLen ) ioCount = (XMP_Int32)length; //smartly avoids 32/64 bit issues
+
+ LFA_Seek ( srcFile, srcOffset, SEEK_SET );
+ LFA_Read ( srcFile, buffer, ioCount, kLFA_RequireAll );
+ LFA_Seek ( dstFile, dstOffset, SEEK_SET );
+ LFA_Write ( dstFile, buffer, ioCount );
+ length -= ioCount;
+
+ srcOffset += ioCount;
+ dstOffset += ioCount;
+
+ }
+
+ } else { // move up -> shift highest packet first
+
+ srcOffset += length; //move to end
+ dstOffset += length;
+
+ while ( length > 0 ) {
+
+ if ( checkAbort && abortProc(abortArg) ) LFA_Throw ( "LFA_Move - User abort", kLFAErr_UserAbort );
+ XMP_Int32 ioCount = kBufferLen;
+ if ( length < kBufferLen ) ioCount = (XMP_Int32)length; //smartly avoids 32/64 bit issues
+
+ srcOffset -= ioCount;
+ dstOffset -= ioCount;
+
+ LFA_Seek ( srcFile, srcOffset, SEEK_SET );
+ LFA_Read ( srcFile, buffer, ioCount, kLFA_RequireAll );
+ LFA_Seek ( dstFile, dstOffset, SEEK_SET );
+ LFA_Write ( dstFile, buffer, ioCount );
+ length -= ioCount;
+
+ }
+
+ }
+
+} // LFA_Move
+
+// =================================================================================================
+
+XMP_Int64 LFA_Tell ( LFA_FileRef file )
+{
+ return LFA_Seek( file, 0 , SEEK_CUR ); // _CUR !
+}
+
+// plain convenience
+XMP_Int64 LFA_Rewind( LFA_FileRef file)
+{
+ return LFA_Seek( file, 0 , SEEK_SET ); // _SET !
+}
+
+//*** kind of a hack, TOTEST
+bool LFA_isEof(LFA_FileRef file)
+{
+#if XMP_MacBuild
+ long refNum = (long)file; // ! Use long to avoid size warnings for SInt16 cast.
+
+ XMP_Int64 position, length;
+ OSErr err = FSGetForkPosition(refNum, &position);
+ if (err != noErr)
+ LFA_Throw("LFA_isEOF:FSGetForkPosition failure", kLFAErr_ExternalFailure);
+
+ err = FSGetForkSize(refNum, &length);
+ if (err != noErr)
+ LFA_Throw("LFA_isEof: FSGetForkSize failure", kLFAErr_ExternalFailure);
+
+ return position == length;
+#endif
+
+#if XMP_WinBuild
+ HANDLE handle = (HANDLE)file;
+
+ XMP_Int64 filepos = LFA_Tell(file);
+ DWORD lowWord, highWord;
+#ifdef WIN_UNIVERSAL_ENV
+ FILE_STANDARD_INFO lpFileStdInfo;
+ GetFileInformationByHandleEx(handle, FileStandardInfo, &lpFileStdInfo, sizeof(FILE_STANDARD_INFO));
+ lowWord = lpFileStdInfo.EndOfFile.u.LowPart;
+ XMP_Int64 highPart = lpFileStdInfo.EndOfFile.u.HighPart;
+ XMP_Int64 filesize = (highPart << 32 | lowWord);
+#else
+ lowWord = GetFileSize(handle, &highWord);
+ XMP_Int64 filesize = (((XMP_Int64)highWord) << 32 | lowWord);
+#endif //WIN_UNIVERSAL_ENV
+ return filesize == filepos;
+#endif
+
+#if XMP_UNIXBuild || XMP_iOSBuild ||XMP_AndroidBuild
+ int descr = (int)(size_t)file;
+
+ struct stat info;
+ if (fstat(descr, &info) == -1)
+ LFA_Throw("LFA_isEof: fstat failed.", kLFAErr_ExternalFailure);
+
+ return LFA_Tell(file) == info.st_size;
+#endif
+}
+// TOTEST
+char LFA_GetChar( LFA_FileRef file )
+{
+ XMP_Uns8 c;
+ LFA_Read( file, &c, 1, true);
+ return c;
+}
diff --git a/samples/source/common/LargeFileAccess.hpp b/samples/source/common/LargeFileAccess.hpp
new file mode 100644
index 0000000..157d263
--- /dev/null
+++ b/samples/source/common/LargeFileAccess.hpp
@@ -0,0 +1,291 @@
+#ifndef __LargeFileAccess_hpp__
+#define __LargeFileAccess_hpp__ 1
+
+// =================================================================================================
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+
+#include <stdio.h> // The assert macro needs printf.
+#include <string>
+
+#if XMP_WinBuild
+ #include <Windows.h>
+ #define snprintf _snprintf
+#else
+ #if XMP_MacBuild
+ #include <CoreServices/CoreServices.h>
+ #endif
+ // POSIX headers for both Mac and generic UNIX.
+ #include <pthread.h>
+ #include <fcntl.h>
+ #include <unistd.h>
+ #include <dirent.h>
+ #include <sys/stat.h>
+ #include <sys/types.h>
+#endif
+
+#include "public/include/XMP_Const.h"
+#include "source/EndianUtils.hpp"
+
+using namespace std;
+
+// =================================================================================================
+
+enum { // Used by LFA_Throw, re-route to whatever you need.
+ kLFAErr_InternalFailure = 1,
+ kLFAErr_ExternalFailure = 2,
+ kLFAErr_UserAbort = 3
+};
+
+void LFA_Throw ( const char* msg, int id );
+
+
+#if ! XMP_UNIXBuild
+ typedef void * LFA_FileRef;
+#else
+ typedef XMP_Int32 LFA_FileRef;
+#endif
+
+// *** Change the model of the LFA functions to not throw for "normal" open/create errors.
+// *** Make sure the semantics of open/create/rename are consistent, e.g. about create of existing.
+
+extern LFA_FileRef LFA_Open ( const char* fileName, char openMode ); // Mode is 'r' or 'w'.
+extern LFA_FileRef LFA_Create ( const char* fileName );
+extern void LFA_Delete ( const char* fileName );
+extern void LFA_Rename ( const char* oldName, const char * newName );
+extern void LFA_Close ( LFA_FileRef file );
+
+#if XMP_MacBuild
+ extern LFA_FileRef LFA_OpenRsrc ( const char * filePath, char mode );
+#endif
+
+// NOTE: unlike the fseek() 'original' LFA_Seek returns the new file position,
+// *NOT* 0 to indicate everything's fine
+extern XMP_Int64 LFA_Seek ( LFA_FileRef file, XMP_Int64 offset, int seekMode, bool* okPtr = 0 );
+extern XMP_Int32 LFA_Read ( LFA_FileRef file, void* buffer, XMP_Int32 bytes, bool requireAll = false );
+extern void LFA_Write ( LFA_FileRef file, const void* buffer, XMP_Int32 bytes );
+extern void LFA_Flush ( LFA_FileRef file );
+extern XMP_Int64 LFA_Tell ( LFA_FileRef file );
+extern XMP_Int64 LFA_Rewind ( LFA_FileRef file );
+extern XMP_Int64 LFA_Measure ( LFA_FileRef file );
+extern void LFA_Extend ( LFA_FileRef file, XMP_Int64 length );
+extern void LFA_Truncate ( LFA_FileRef file, XMP_Int64 length );
+
+extern void LFA_Copy ( LFA_FileRef sourceFile, LFA_FileRef destFile, XMP_Int64 length, // Not a primitive.
+ XMP_AbortProc abortProc = 0, void * abortArg = 0 );
+
+/* move stuff within a file (both upward and downward */
+extern void LFA_Move ( LFA_FileRef srcFile, XMP_Int64 srcOffset, LFA_FileRef dstFile, XMP_Int64 dstOffset,
+ XMP_Int64 length, XMP_AbortProc abortProc = 0, void * abortArg = 0 );
+
+extern bool LFA_isEof ( LFA_FileRef file );
+extern char LFA_GetChar ( LFA_FileRef file );
+
+enum { kLFA_RequireAll = true }; // Used for requireAll to LFA_Read.
+
+// =================================================================================================
+// Read and convert endianess in one go:
+static inline XMP_Uns8 LFA_ReadUns8 ( LFA_FileRef file )
+{
+ XMP_Uns8 value;
+ LFA_Read ( file, &value, 1, kLFA_RequireAll );
+ return value;
+}
+
+static inline XMP_Uns16 LFA_ReadUns16_BE ( LFA_FileRef file )
+{
+ XMP_Uns16 value;
+ LFA_Read ( file, &value, 2, kLFA_RequireAll );
+ return MakeUns16BE ( value );
+}
+
+static inline XMP_Uns16 LFA_ReadUns16_LE ( LFA_FileRef file )
+{
+ XMP_Uns16 value;
+ LFA_Read ( file, &value, 2, kLFA_RequireAll );
+ return MakeUns16LE ( value );
+}
+
+static inline XMP_Uns32 LFA_ReadUns32_BE ( LFA_FileRef file )
+{
+ XMP_Uns32 value;
+ LFA_Read ( file, &value, 4, kLFA_RequireAll );
+ return MakeUns32BE ( value );
+}
+
+static inline XMP_Uns32 LFA_ReadUns32_LE ( LFA_FileRef file )
+{
+ XMP_Uns32 value;
+ LFA_Read ( file, &value, 4, kLFA_RequireAll );
+ return MakeUns32LE ( value );
+}
+
+// new:
+static inline XMP_Uns64 LFA_ReadUns64_BE ( LFA_FileRef file )
+{
+ XMP_Uns64 value;
+ LFA_Read ( file, &value, 8, kLFA_RequireAll );
+ return MakeUns64BE ( value );
+}
+
+static inline XMP_Uns64 LFA_ReadUns64_LE ( LFA_FileRef file )
+{
+ XMP_Uns64 value;
+ LFA_Read ( file, &value, 8, kLFA_RequireAll );
+ return MakeUns64LE ( value );
+}
+
+#define LFA_ReadInt16_BE(file) ((XMP_Int16) LFA_ReadUns16_BE ( file ))
+#define LFA_ReadInt16_LE(file) ((XMP_Int16) LFA_ReadUns16_LE ( file ))
+#define LFA_ReadInt32_BE(file) ((XMP_Int32) LFA_ReadUns32_BE ( file ))
+#define LFA_ReadInt32_LE(file) ((XMP_Int32) LFA_ReadUns32_LE ( file ))
+#define LFA_ReadInt64_BE(file) ((XMP_Int64) LFA_ReadUns64_BE ( file ))
+#define LFA_ReadInt64_LE(file) ((XMP_Int64) LFA_ReadUns64_LE ( file ))
+
+// peek functions /////////////////////////////////////////////
+// peek functions, to see what's next:
+static inline XMP_Uns8 LFA_PeekUns8(LFA_FileRef file)
+{
+ XMP_Uns8 value = LFA_ReadUns8( file );
+ LFA_Seek( file, -1, SEEK_CUR );
+ return value;
+}
+
+static inline XMP_Uns16 LFA_PeekUns16_BE(LFA_FileRef file)
+{
+ XMP_Uns16 value = LFA_ReadUns16_BE( file );
+ LFA_Seek( file, -2, SEEK_CUR );
+ return value;
+}
+
+static inline XMP_Uns16 LFA_PeekUns16_LE(LFA_FileRef file)
+{
+ XMP_Uns16 value = LFA_ReadUns16_LE( file );
+ LFA_Seek( file, -2, SEEK_CUR );
+ return value;
+}
+
+static inline XMP_Uns32 LFA_PeekUns32_BE(LFA_FileRef file)
+{
+ XMP_Uns32 value = LFA_ReadUns32_BE( file );
+ LFA_Seek( file, -4, SEEK_CUR );
+ return value;
+}
+
+static inline XMP_Uns32 LFA_PeekUns32_LE(LFA_FileRef file)
+{
+ XMP_Uns32 value = LFA_ReadUns32_LE( file );
+ LFA_Seek( file, -4, SEEK_CUR );
+ return value;
+}
+
+static inline XMP_Uns64 LFA_PeekUns64_BE(LFA_FileRef file)
+{
+ XMP_Uns64 value = LFA_ReadUns64_BE( file );
+ LFA_Seek( file, -8, SEEK_CUR );
+ return value;
+}
+
+static inline XMP_Uns64 LFA_PeekUns64_LE(LFA_FileRef file)
+{
+ XMP_Uns64 value = LFA_ReadUns64_LE( file );
+ LFA_Seek( file, -8, SEEK_CUR );
+ return value;
+}
+
+
+#define LFA_PeekInt16_BE(file) ((XMP_Int16) LFA_PeekUns16_BE ( file ))
+#define LFA_PeekInt16_LE(file) ((XMP_Int16) LFA_PeekUns16_LE ( file ))
+#define LFA_PeekInt32_BE(file) ((XMP_Int32) LFA_PeekUns32_BE ( file ))
+#define LFA_PeekInt32_LE(file) ((XMP_Int32) LFA_PeekUns32_LE ( file ))
+#define LFA_PeekInt64_BE(file) ((XMP_Int64) LFA_PeekUns64_BE ( file ))
+#define LFA_PeekInt64_LE(file) ((XMP_Int64) LFA_PeekUns64_LE ( file ))
+
+// write functions /////////////////////////////////////////////
+static inline void LFA_WriteUns8 ( LFA_FileRef file, XMP_Uns8 value )
+{
+ LFA_Write ( file, &value, 1 );
+}
+
+static inline void LFA_WriteUns16_LE ( LFA_FileRef file, XMP_Uns16 value )
+{
+ XMP_Uns16 v = MakeUns16LE(value);
+ LFA_Write ( file, &v, 2 );
+}
+
+static inline void LFA_WriteUns16_BE ( LFA_FileRef file, XMP_Uns16 value )
+{
+ XMP_Uns16 v = MakeUns16BE(value);
+ LFA_Write ( file, &v, 2 );
+}
+
+static inline void LFA_WriteUns32_LE ( LFA_FileRef file, XMP_Uns32 value )
+{
+ XMP_Uns32 v = MakeUns32LE(value);
+ LFA_Write ( file, &v, 4 );
+}
+
+static inline void LFA_WriteUns32_BE ( LFA_FileRef file, XMP_Uns32 value )
+{
+ XMP_Uns32 v = MakeUns32BE(value);
+ LFA_Write ( file, &v, 4 );
+}
+
+static inline void LFA_WriteUns64_LE ( LFA_FileRef file, XMP_Uns64 value )
+{
+ XMP_Uns64 v = MakeUns64LE(value);
+ LFA_Write ( file, &v, 8 );
+}
+
+static inline void LFA_WriteUns64_BE ( LFA_FileRef file, XMP_Uns64 value )
+{
+ XMP_Uns64 v = MakeUns64BE(value);
+ LFA_Write ( file, &v, 8 );
+}
+
+////////////////////////////////////////
+
+static inline void LFA_WriteInt16_LE ( LFA_FileRef file, XMP_Int16 value )
+{
+ XMP_Uns16 v = MakeUns16LE((XMP_Uns16)value);
+ LFA_Write ( file, &v, 2 );
+}
+
+static inline void LFA_WriteInt16_BE ( LFA_FileRef file, XMP_Int16 value )
+{
+ XMP_Uns16 v = MakeUns16BE((XMP_Uns16)value);
+ LFA_Write ( file, &v, 2 );
+}
+
+static inline void LFA_WriteInt32_LE ( LFA_FileRef file, XMP_Int32 value )
+{
+ XMP_Uns32 v = MakeUns32LE((XMP_Uns32)value);
+ LFA_Write ( file, &v, 4 );
+}
+
+static inline void LFA_WriteInt32_BE ( LFA_FileRef file, XMP_Int32 value )
+{
+ XMP_Uns32 v = MakeUns32BE((XMP_Uns32)value);
+ LFA_Write ( file, &v, 4 );
+}
+
+static inline void LFA_WriteInt64_LE ( LFA_FileRef file, XMP_Int64 value )
+{
+ XMP_Uns64 v = MakeUns64LE((XMP_Uns64)value);
+ LFA_Write ( file, &v, 8 );
+}
+
+static inline void LFA_WriteInt64_BE ( LFA_FileRef file, XMP_Int64 value )
+{
+ XMP_Uns64 v = MakeUns64BE((XMP_Uns64)value);
+ LFA_Write ( file, &v, 8 );
+}
+
+
+#endif // __LargeFileAccess_hpp__
diff --git a/samples/source/common/Log.cpp b/samples/source/common/Log.cpp
new file mode 100644
index 0000000..b39ecd1
--- /dev/null
+++ b/samples/source/common/Log.cpp
@@ -0,0 +1,252 @@
+// =================================================================================================
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+
+/*
+*
+* a little set of functions for logging info, warnings, errors either to
+* stdout or a file (this is no class on purpose, to avoid having to pass
+* a particular instance on and on...)
+*
+* you can call the (static) log funcitons right away (resulting in stdout)
+* or create one (1!, singleton) instance first telling the outfile
+* recommendation: do that as a scoped variable not using new, since ~destruction == proper file closure...
+* (following exception-safe RAII-philosophy http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization )
+*
+*/
+
+#ifdef WIN32
+ #pragma warning ( disable : 4996 ) // fopen was declared deprecated...
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "samples/source/common/globals.h"
+#include "samples/source/common/Log.h"
+using namespace std;
+
+//static variables ////////////////////////////////////////////////////////////////////
+FILE* Log::logfile=NULL;
+bool Log::noErrorsOrWarnings=true;
+bool Log::mute=false;
+bool Log::muteWarnings=false;
+
+// strings for skipped test information
+std::string Log::skippedTestsAll;
+std::string Log::skippedTestsWin;
+std::string Log::skippedTestsMac;
+std::string Log::skippedTestsUnix;
+//////////////////////////////////////////////////////////////////////////////////////
+
+/* standard log, use filename=NULL or filename="" or do not construct at all
+ * for logging to stdout only
+ * (otherwise stuff gets logged to stdout *and* to the logfile specified)
+ */
+Log::Log(const char* filename)
+{
+ if (Log::logfile) { //enfore singleton
+ printf("tried to create two logs (forbidden, singleton)");
+ throw LOG_Exception("tried to create two logs (forbidden, singleton)");
+ }
+ if (!filename)
+ logfile=NULL; //OLD: logfile=stdout;
+ else if (!strcmp(filename,""))
+ logfile=NULL; //empty string
+ else {
+ logfile=fopen(filename,"wt");
+ if (!logfile) {
+ printf("could not open output file %s for writing",filename); //do in addition to make sure it's output
+ throw LOG_Exception("could not open output file for writing");
+ }
+ }
+}
+
+Log::~Log()
+{
+ if(logfile) {
+ //info("logfile properly closed.");
+ fclose(logfile);
+ logfile=NULL; //BUGSOURCE: null out such that any possible following traces do not go to invalid file...
+ }
+}
+
+/*
+* outputs to file and standardout
+* (Note: it would be wunderbar to group the essential output into one function,
+* regretably that type for "..."-paramter forwarding doesn't go down well with logging MACROS (__DATE etc...)
+* thus this may be the best solution
+*/
+
+void Log::trace(const char* format, ...) {
+ if (Log::mute) return;
+ va_list args; va_start(args, format);
+ //stdout
+ vprintf( format, args);
+ printf( "\n");
+ //file
+ if(logfile) {
+ vfprintf( logfile, format, args);
+ fprintf( logfile, "\n");
+ }
+ va_end(args);
+}
+
+void Log::info(const char* format, ...) {
+ // experience from TagTree, not encountered in Log.
+ //note: format and ... are somehow "used up", i.e. dumping them
+ // via vsprintf _and_ via printf brought up errors on Mac (only)
+ // i.e. %d %X stuff looking odd (roughly like signed vs unsigned...)
+ // buffer reuse as in (2) is fine, just dont use format/... twice.
+
+ if (Log::mute) return;
+ va_list args; va_start(args, format);
+ //stdout
+ vprintf( format, args);
+ printf( "\n");
+ //file
+ if(logfile) {
+ vfprintf( logfile, format, args);
+ fprintf( logfile, "\n");
+ }
+ va_end(args);
+}
+
+void Log::important(const char* format, ...) {
+ va_list args; va_start(args, format);
+ //stdout
+ vprintf( format, args);
+ printf( "\n");
+ //file
+ if(logfile) {
+ vfprintf( logfile, format, args);
+ fprintf( logfile, "\n");
+ }
+ va_end(args);
+}
+
+
+void Log::infoNoLF(const char* format, ...) {
+ if (Log::mute) return;
+ va_list args; va_start(args, format);
+ //stdout
+ vprintf( format, args);
+ //file
+ if(logfile) {
+ vfprintf( logfile, format, args);
+ }
+ va_end(args);
+}
+
+void Log::warn(const char* format, ...){
+ if (Log::muteWarnings) return; //bogus warnings or neither output nor do they 'count'
+
+ noErrorsOrWarnings=false; //too bad...
+ va_list args; va_start(args, format);
+ //stdout
+ printf( "warning:" );
+ vprintf( format, args);
+ printf( "\n");
+ //file
+ if(logfile) {
+ fprintf( logfile, "warning:");
+ vfprintf( logfile, format, args);
+ fprintf( logfile, "\n");
+ }
+ va_end(args);
+}
+
+void Log::error(const char* format, ...){
+ noErrorsOrWarnings=false; //too bad...
+ va_list args; va_start(args, format);
+ //since many errors in test are caused by intend in testing
+ //(so you don't want to see them), do not output directly here,
+ //but leave that to a particular dump-Function
+ //(which should be part of any catch(LOG_Exception))
+ char buffer [XMPQE_MAX_ERROR_LENGTH]; //let's hope that fits (see .h file)
+ vsnprintf( buffer, XMPQE_MAX_ERROR_LENGTH, format, args);
+ //if not existing on sme unix consider going back to (minimally less secure vsprintf (w/o the n))
+ va_end(args);
+ throw LOG_Exception(buffer);
+ //may throw exception ONLY, no exit()-call, for cppunit's sake
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+//convenience overloads
+
+void Log::info(std::string s1)
+{
+ if (Log::mute) return;
+ info(s1.c_str());
+}
+
+void Log::important(std::string s1)
+{
+ info(s1.c_str());
+}
+
+void Log::error( std::string s1 )
+{
+ error( s1.c_str() );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// skipped tests
+void Log::printSkippedTest(const char* filename)
+{
+ std::string completeString = "<html><head><title>Skipped Tests:</title><head><body>";
+ if (!skippedTestsAll.empty())
+ completeString.append("<br><br><table border=\"1\"><caption>all Plattforms:</caption><tr><th>Path to Test</th><th>comment</th></tr>"+skippedTestsAll + "</table>");
+ if (!skippedTestsWin.empty())
+ completeString.append("<br><br><table border=\"1\"><caption>Windows:</caption><tr><th>Path to Test</th><th>comment</th></tr>"+skippedTestsWin+ "</table>");
+ if (!skippedTestsMac.empty())
+ completeString.append("<br><br><table border=\"1\"><caption>Macintosh:</caption><tr><th>Path to Test</th><th>comment</th></tr>"+skippedTestsMac+ "</table>");
+ if (!skippedTestsUnix.empty())
+ completeString.append("<br><br><table border=\"1\"><caption>UNIX:</caption><tr><th>Path to Test</th><th>comment</th></tr>"+skippedTestsUnix+ "</table>");
+
+ completeString.append("</body></html>");
+ // print to console if mute is off
+ /*if (!Log::mute)
+ printf(completeString.c_str());*/
+
+ //print result to file
+ FILE* outputFile=fopen(filename,"wt");
+ if (!outputFile) {
+ printf("could not open output file %s for writing",filename); //do in addition to make sure it's output
+ throw LOG_Exception("could not open output file for writing");
+ }
+ fprintf( outputFile, "%s", completeString.c_str());
+ if(outputFile) {
+ fclose(outputFile);
+ }
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+// mute vs. verbose
+
+void Log::setMute()
+{
+ Log::mute = true;
+}
+
+void Log::setVerbose()
+{
+ Log::mute = false;
+}
+
+void Log::setMuteWarnings()
+{
+ Log::muteWarnings = true;
+}
+
+void Log::setVerboseWarnings()
+{
+ Log::muteWarnings = false;
+}
diff --git a/samples/source/common/Log.h b/samples/source/common/Log.h
new file mode 100644
index 0000000..e6e0a49
--- /dev/null
+++ b/samples/source/common/Log.h
@@ -0,0 +1,97 @@
+// =================================================================================================
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+
+#ifndef __Log_h__
+#define __Log_h__ 1
+
+#include "public/include/XMP_Environment.h"
+
+#include <stdexcept> // (xcode needs stdexcept to accept class declaration below )
+#include <string>
+
+
+#if XMP_WinBuild
+ #pragma warning ( disable : 4996 ) // Consider using _snprintf_s instead.
+ #define snprintf _snprintf
+ //#define assertMsg(msg,c) \
+ //if ( ! (c) ) { \
+ // Log::error ( "- assert that failed: %s\n- message: %s\n- " __FILE__ ":%u", #c, std::string( msg ).c_str(), __LINE__ ); \
+ // /* Log::error throws the exception which either fails the testcase resp. terminates dumpfile */ \
+ // /* important to do it this way such that both testrunner and standalone use is possible */ \
+ //}
+#endif
+
+//8K hopefully does for any type of error message
+#define XMPQE_MAX_ERROR_LENGTH 8*1048
+
+class LOG_Exception : public std::runtime_error {
+public:
+ LOG_Exception(const char* errorMsg) : std::runtime_error(errorMsg) { }
+};
+
+class Log {
+ public:
+ Log(const char* filename);
+ ~Log(void);
+
+ // trace is identical to info, except that it can be turned of (soon)
+ // ==> use trace for any non-crucial information
+ // i.e. bits and pieces in a test
+ // do not use for parts of known-good-output, essential summaries, etc...
+ static void trace(const char* format, ...);
+
+ // be aware of bug1741056, feeding 64bit numbers might not output correctly
+ static void info(const char* format, ...);
+ static void infoNoLF(const char* format, ...);
+ static void warn(const char* format, ...);
+ static void error(const char* format, ...);
+ static bool noErrorsOrWarnings; //set to true, if any warnings occured
+
+ //identical to info, but immune to "mute"
+ // be aware of bug1741056, feeding 64bit numbers might not output correctly
+ static void important(const char* format, ...);
+
+ //convenience overloads:
+ static void info(std::string s1);
+ static void important(std::string s1);
+ static void error( std::string s1 );
+
+ // mute vs. verbose
+ static void setMute();
+ static void setVerbose();
+
+ static void setMuteWarnings();
+ static void setVerboseWarnings();
+
+ static bool mute;
+ // NB: public on intend, such that i.e. copy commands can decide to
+ // send their output to nirvana accordingly on mute==true;
+ // (getter/setter on a static boolean var is fairly pointless)
+
+ // strings to store the skipped test for different plattforms
+ static std::string skippedTestsAll;
+ static std::string skippedTestsWin;
+ static std::string skippedTestsMac;
+ static std::string skippedTestsUnix;
+
+ // function to print the skipped tests on console and into a log file
+ static void printSkippedTest(const char* filename);
+
+ private:
+ static bool singletonInstanciated;
+ static FILE* logfile;
+
+ //should only be temporarily used, i.e. on selftests that
+ //inevitable yield (during selftet: bogus) warnings
+ static bool muteWarnings;
+
+
+};
+
+#endif
diff --git a/samples/source/common/TagTree.cpp b/samples/source/common/TagTree.cpp
new file mode 100644
index 0000000..5b6cc75
--- /dev/null
+++ b/samples/source/common/TagTree.cpp
@@ -0,0 +1,760 @@
+// =================================================================================================
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+
+/*
+* all legacy metadata comes in some hierarchical form
+* these routines help generate a tree, which is subsequently parseable (the "classic" dumpImage)
+
+* as well as queriable (primitive but workable solution)
+* having direct access to particluar legacy fields through unique keys (like "psir424")
+* enables efficient testing. tagMap is used for that.
+*
+* if keys are defined multiple times, the are named sameKey, sameKey-2, sameKey-3, sameKey-4
+* allowing also specific queries on those
+*
+*/
+
+#include "samples/source/common/TagTree.h"
+#include <stdarg.h>
+
+// silent by default
+bool TagTree::verbose = false;
+
+#if WIN_ENV
+ //should preferably be within a #if XMP_WinBuild, not doable here...
+ #pragma warning ( disable : 4996 ) // 'sprintf' was declared deprecated
+
+ #include <stdio.h>
+ #ifndef snprintf
+ #define snprintf _xmp_snprintf
+ #pragma warning ( disable : 4996 ) // Consider using _snprintf_s instead.
+
+ void _xmp_snprintf(char * buffer,size_t len,const char * format, ...)
+ {
+ va_list args; va_start(args, format);
+ _snprintf( buffer, len, format, args);
+ // using bad windows routine from stdio.h
+ // and indeed named, _snprintf, no such thing as snprintf
+ buffer[len-1] = 0; //write that trailing zero..
+ va_end(args);
+ }
+ #endif
+#endif
+
+//private - duplicate of Utils routine
+std::string itos(int i)
+{
+ char cs[31]; //worst to come is 64 bit <=> 21 bytes (more likely will be 32 bit forever)
+ snprintf(cs,30,"%d",i); //snprintf does buffer overrun check which sprintf doesn't
+ return std::string(cs);
+}
+
+TagTree::TagTree()
+{
+ rootNode.key="rootkey"; //all never seen
+ rootNode.value="rootvalue";
+ rootNode.comment="rootcomment";
+ reset();
+}
+
+TagTree::~TagTree()
+{
+
+}
+
+void TagTree::reset()
+{
+ tagMap.clear();
+ nodeStack.clear();
+ rootNode.children.clear();
+ nodeStack.push_back(&rootNode);
+ lastNode = NULL;
+}
+
+
+// verbosity control:
+void TagTree::setMute()
+{
+ TagTree::verbose = false;
+}
+
+void TagTree::setVerbose()
+{
+ TagTree::verbose = true;
+}
+
+
+void TagTree::setKeyValue(const std::string key,const std::string value, const std::string _comment)
+{
+ Node* pCurNode=*nodeStack.rbegin(); //current Node
+ pCurNode->children.push_back(Node(key,value, _comment));
+
+ if(key.size()==0) { // standalone comment?
+ if (value.size()!=0) // must have no value
+ Log::error("no key but value found.");
+ return; // ==> do not add to tag-map
+ }
+
+ //find first free foo, foo-2, foo-3 (note: no foo-1)
+ int i=1;
+ std::string extkey=key;
+ while( tagMap.count(extkey) )
+ {
+ i++;
+ extkey = key + "-" + itos(i);
+ }
+
+ //add to Map -----------------------------------
+ lastNode=&*(pCurNode->children.rbegin());
+ tagMap[extkey]=lastNode;
+
+ if ( verbose )
+ Log::info( " setKeyValue( %s |-> %s) [%s]", key.c_str(), value.c_str(), _comment.c_str() );
+}
+
+void TagTree::updateKeyValue ( const std::string key, const std::string value, const std::string _comment )
+{
+ Node* pCurNode = *nodeStack.rbegin ( ); //current Node
+ pCurNode->children.push_back ( Node ( key, value, _comment ) );
+
+ if ( key.size ( ) == 0 ) { // standalone comment?
+ if ( value.size ( ) != 0 ) // must have no value
+ Log::error ( "no key but value found." );
+ return; // ==> do not add to tag-map
+ }
+
+ //add to Map -----------------------------------
+ lastNode = &*(pCurNode->children.rbegin ( ));
+ tagMap[key] = lastNode;
+
+ if ( verbose )
+ Log::info ( " setKeyValue( %s |-> %s) [%s]", key.c_str ( ), value.c_str ( ), _comment.c_str ( ) );
+}
+
+void TagTree::digest(LFA_FileRef file,const std::string key /*=NULL*/,
+ void* returnValue /*=""*/,
+ XMP_Int32 numOfBytes /*=0*/ )
+{
+ if (numOfBytes==0) {
+ //0-byte requests *are* legitimate, reducing codeforks for the caller
+ if ( !key.empty() )
+ setKeyValue(key,"(0 bytes)");
+ return;
+ }
+
+ //do we need own space or will it be provided?
+ char* value;
+ if (returnValue)
+ value=(char*)returnValue;
+ else
+ value=new char[numOfBytes+1];
+
+ // require all == false => leave the throwing to this routine
+ if (numOfBytes != LFA_Read ( file, value, numOfBytes, false)) // saying 1,4 guarantes read as ordered (4,1 would not)
+ Log::error("could not read %d number of files (End of File reached?)",numOfBytes);
+#if !IOS_ENV && !WIN_UNIVERSAL_ENV
+ char* out=new char[2 + numOfBytes*3 + 5]; //'0x12 34 45 78 ' length formula: 2 ("0x") + numOfBytes x 3 + 5 (padding)
+ if (!key.empty()) {
+ snprintf(out,3,"0x");
+ XMP_Int64 i; // *)
+ for (i = 0; i < numOfBytes; i++)
+ {
+ snprintf(&out[2 + i * 3], 4, "%.2X ", value[i]); //always must allow that extra 0-byte on mac (overwritten again and again)
+ }
+ snprintf(&out[2+i*3],1,"%c",'\0'); // *) using i one more time (needed while bug 1613297 regarding snprintf not fixed)
+ setKeyValue(key,out);
+ }
+#else
+ char* out=new char[2 + numOfBytes*9 + 5]; //'0x12 34 45 78 ' length formula: 2 ("0x") + numOfBytes x 3 + 5 (padding)
+ if (!key.empty()) {
+ snprintf(out,3,"0x");
+ XMP_Int64 i; // *)
+ for (i=0; i < numOfBytes; i++)
+ snprintf(&out[2+i*9],10,"%.8X ",value[i]); //always must allow that extra 0-byte on mac (overwritten again and again)
+ snprintf(&out[2+i*9],1,"%c",'\0'); // *) using i one more time (needed while bug 1613297 regarding snprintf not fixed)
+ setKeyValue(key,out);
+ }
+
+#endif
+ delete[] out;
+ if (!returnValue) delete value; //if we own it, we delete it
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// numeric digest routines
+//
+XMP_Int64 TagTree::digest64s(LFA_FileRef file,const std::string key /* ="" */ , bool BigEndian /*=false*/ )
+{
+ XMP_Int64 r;
+ if (8 != LFA_Read ( file, &r, 8, false)) // require all == false => leave the throwing to this routine
+ Log::error("could not read 8-byte value from file (end of file?)");
+ if ( ((kBigEndianHost==1) && !BigEndian ) || ((kBigEndianHost==0) && BigEndian )) // "XOR"
+ Flip8(&r);
+
+ if (!key.empty()) {
+ char out[25]; //longest is "18446744073709551615", 21 chars ==> 25
+ snprintf(out,24,"%lld",r); //signed, mind the trailing \0 on Mac btw
+ setKeyValue(key,out);
+ }
+ return r;
+}
+
+XMP_Uns64 TagTree::digest64u(LFA_FileRef file,const std::string key /* ="" */, bool BigEndian /*=false*/,bool hexDisplay /*=false*/ )
+{
+ XMP_Uns64 r;
+ if (8 != LFA_Read ( file, &r, 8, false)) // require all == false => leave the throwing to this routine
+ Log::error("could not read 8-byte value from file (end of file?)");
+ if ( ((kBigEndianHost==1) && !BigEndian ) || ((kBigEndianHost==0) && BigEndian )) // "XOR"
+ Flip8(&r);
+ if (!key.empty()) {
+ char out[25]; //largets 64 bit no: 18446744073709551616 -1 (20 digits)
+ if (!hexDisplay)
+ {
+ //not working, 0x1244e7780 ==> 609122176 decimal (== 0x244e7780)
+ #if WIN_ENV
+ snprintf(out , 24 , "%I64u" , r);
+ #else
+ // MAC, UNIX
+ snprintf(out , 24 , "%llu" , r);
+ #endif
+ }
+ else
+ {
+ //not working, upper 32 bit empty:
+ #if WIN_ENV
+ snprintf( out , 24 , "0x%.16I64X" , r );
+ #else
+ snprintf( out , 24 , "0x%.16llX" , r );
+ #endif
+ }
+ setKeyValue(key,out);
+ }
+ return r;
+}
+
+
+XMP_Int32 TagTree::digest32s(LFA_FileRef file,const std::string key /* ="" */ , bool BigEndian /*=false*/ )
+{
+ XMP_Int32 r;
+ if (4 != LFA_Read ( file, &r, 4, false)) // require all == false => leave the throwing to this routine
+ Log::error("could not read 4-byte value from file (end of file?)");
+ if ( ((kBigEndianHost==1) && !BigEndian ) || ((kBigEndianHost==0) && BigEndian )) // "XOR"
+ Flip4(&r);
+ if (!key.empty()) {
+ char out[15]; //longest signed int is "-2147483648", 11 chars
+ snprintf(out,14,"%d",r); //signed, mind the trailing \0 on Mac btw
+ setKeyValue(key,out);
+ }
+ return r;
+}
+
+XMP_Uns32 TagTree::digest32u(LFA_FileRef file,const std::string key /* ="" */, bool BigEndian /*=false*/,bool hexDisplay /*=false*/ )
+{
+ XMP_Uns32 r;
+ if (4 != LFA_Read ( file, &r, 4, false)) // require all == false => leave the throwing to this routine
+ Log::error("could not read 4-byte value from file (end of file?)");
+ if ( ((kBigEndianHost==1) && !BigEndian ) || ((kBigEndianHost==0) && BigEndian )) // "XOR"
+ Flip4(&r);
+ if (!key.empty()) {
+ char out[19]; //longest unsigned int is "2147483648", 10 chars resp. 0xFFFFFFFF 10 chars
+ if (!hexDisplay)
+ snprintf(out,18,"%u",r); //unsigned, mind the trailing \0 on Mac btw
+ else
+ snprintf(out,18,"0x%.8X",r); //unsigned, mind the trailing \0 on Mac btw
+ setKeyValue(key,out);
+ }
+ return r;
+}
+//////////////////
+XMP_Int16 TagTree::digest16s(LFA_FileRef file,const std::string key /* ="" */ , bool BigEndian /*=false*/ )
+{
+ XMP_Int16 r;
+ if (2 != LFA_Read ( file, &r, 2, false)) // require all == false => leave the throwing to this routine
+ Log::error("could not read 2-byte value from file (end of file?)");
+ if ( ((kBigEndianHost==1) && !BigEndian ) || ((kBigEndianHost==0) && BigEndian )) // "XOR"
+ Flip2(&r);
+ if (!key.empty()) {
+ char out[10]; //longest signed int is "�32768", 6 chars
+ snprintf(out,9,"%d",r);
+ setKeyValue(key,out);
+ }
+ return r;
+}
+
+XMP_Uns16 TagTree::digest16u(LFA_FileRef file,const std::string key /* ="" */, bool BigEndian /*=false*/,bool hexDisplay /*=false*/ )
+{
+ XMP_Uns16 r;
+ if (2 != LFA_Read ( file, &r, 2, false)) // require all == false => leave the throwing to this routine
+ Log::error("could not read 2-byte value from file (end of file?)");
+ if ( ((kBigEndianHost==1) && !BigEndian ) || ((kBigEndianHost==0) && BigEndian )) // "XOR"
+ Flip2(&r);
+ if (!key.empty()) {
+ char out[15]; //longest unsigned int is "65536", 5 chars resp. 0xFFFF = 6 chars
+ if (!hexDisplay)
+ snprintf(out,14,"%u",r);
+ else
+ snprintf(out,14,"0x%.4X",r);
+ setKeyValue(key,out);
+ }
+ return r;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+// "expected" Overrides
+void TagTree::digest64s(XMP_Int64 expected, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/ )
+{
+ XMP_Int64 tmp=digest64s(file,"",BigEndian);
+ if ( expected != tmp )
+ throw DumpFileException("'%s' was %d, expected: %d",key.c_str(),tmp,expected);
+}
+
+void TagTree::digest64u(XMP_Uns64 expected, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/, bool hexDisplay /*=false*/)
+{
+ XMP_Uns64 tmp=digest64u( file,"",BigEndian, hexDisplay );
+ if (expected != tmp )
+ {
+ if (hexDisplay)
+ {
+ throw DumpFileException("'%s' was 0x%.16X, expected: 0x%.16X",key.c_str(),tmp,expected);
+ }
+ else
+ {
+ throw DumpFileException("'%s' was %d, expected: %d",key.c_str(),tmp,expected);
+ }
+ }
+}
+
+void TagTree::digest32s(XMP_Int32 expected, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/ )
+{
+ XMP_Int32 tmp=digest32s(file,"",BigEndian);
+ if ( expected != tmp )
+ throw DumpFileException("'%s' was %d, expected: %d",key.c_str(),tmp,expected);
+}
+
+void TagTree::digest32u(XMP_Uns32 expected, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/, bool hexDisplay /*=false*/)
+{
+ XMP_Uns32 tmp=digest32u( file,"",BigEndian, hexDisplay );
+ if (expected != tmp )
+ {
+ if (hexDisplay)
+ {
+ throw DumpFileException("'%s' was 0x%.8X, expected: 0x%.8X",key.c_str(),tmp,expected);
+ }
+ else
+ {
+ throw DumpFileException("'%s' was %d, expected: %d",key.c_str(),tmp,expected);
+ }
+ }
+}
+
+void TagTree::digest16s(XMP_Int16 expected, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/ )
+{
+ XMP_Int16 tmp=digest16s(file,key,BigEndian);
+ if ( expected != tmp )
+ throw DumpFileException("'%s' was %d, expected: %d",key.c_str(),tmp,expected);
+}
+
+void TagTree::digest16u(XMP_Uns16 expected, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/, bool hexDisplay /*=false*/)
+{
+ XMP_Uns16 tmp=digest16u( file,key,BigEndian, hexDisplay );
+ if (expected != tmp )
+ {
+ if (hexDisplay)
+ {
+ throw DumpFileException("'%s' was 0x%.4X, expected: 0x%.4X",key.c_str(),tmp,expected);
+ }
+ else
+ {
+ throw DumpFileException("'%s' was %d, expected: %d",key.c_str(),tmp,expected);
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+// CBR Overrides
+void TagTree::digest64s(XMP_Int64* returnValue, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/ )
+{
+ *returnValue = digest64s(file,key,BigEndian);
+}
+
+void TagTree::digest64u(XMP_Uns64* returnValue, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/, bool hexDisplay /*=false*/)
+{
+ *returnValue = digest64u( file, key,BigEndian, hexDisplay );
+}
+
+void TagTree::digest32s(XMP_Int32* returnValue, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/ )
+{
+ *returnValue = digest32s(file,key,BigEndian);
+}
+
+void TagTree::digest32u(XMP_Uns32* returnValue, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/, bool hexDisplay /*=false*/)
+{
+ *returnValue = digest32u( file, key,BigEndian, hexDisplay );
+}
+
+void TagTree::digest16s(XMP_Int16* returnValue, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/ )
+{
+ *returnValue = digest16s(file,key,BigEndian);
+}
+
+void TagTree::digest16u(XMP_Uns16* returnValue, LFA_FileRef file,const std::string key /*=""*/, bool BigEndian /*=false*/, bool hexDisplay /*=false*/)
+{
+ *returnValue = digest16u( file, key,BigEndian, hexDisplay );
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
+std::string TagTree::digestString(LFA_FileRef file,const std::string key /*=""*/, size_t length /* =0 */, bool verifyZeroTerm /* =false */, bool allowEarlyZeroTerm /* =false */ )
+{
+ std::string r(256,'\0'); //give some room in advance (performance)
+ r.clear(); // safety measure (may be needed on mac)
+
+ bool outside = false; // toggle-flag: outside ASCII
+
+ for ( XMP_Uns32 i = 0; ( i<length ) || (length==0) ; i++ )
+ {
+ XMP_Uns8 ch = (XMP_Uns8)LFA_GetChar(file);
+
+ // allow early zero termination (useful for fixed length field that may or may not end prematurely)
+ if ( allowEarlyZeroTerm && ( ch == 0 ) && ( length != 0 ) )
+ {
+ i++;
+ LFA_Seek( file, length - i, SEEK_CUR ); // compensate for skipped bytes
+ break;
+ }
+
+ if ( (0x20 <= ch) && (ch <= 0x7E) )
+ { //outside-case
+ if ( outside )
+ r.push_back('>');
+ r.push_back(ch);
+ outside = false;
+ } else {
+ if ( (length==0) && (ch == '\0' ) )
+ break; // lenght zero => watch for zero termination...
+ if ( !outside )
+ r.push_back('<'); //first inside
+ else if (!((length==0) && (ch =='\0')))
+ r.push_back(' '); //further inside (except very last)
+ outside = true;
+ char tmp[4];
+ sprintf(tmp, "%.2X", ch );
+ r+=tmp;
+ }
+ }
+
+ if ( outside ) r.push_back('>'); //last one
+
+ if ( verifyZeroTerm )
+ {
+ XMP_Uns8 ch = (XMP_Uns8)LFA_GetChar(file);
+ if ( ch != 0 )
+ Log::error("string for key %s not terminated with zero as requested but with 0x%.2X",key.c_str(),ch);
+ }
+
+
+ if (!key.empty())
+ setKeyValue(key,r);
+
+ return r;
+}
+
+void TagTree::comment(const std::string _comment)
+{
+ setKeyValue("","", _comment);
+}
+
+void TagTree::comment(const char* format, ...)
+{
+ char buffer[XMPQE_BUFFERSIZE];
+ va_list args;
+ va_start(args, format);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ setKeyValue("","",buffer);
+}
+
+//adding a subnode to tagMap and current node
+//(do "go in", pushes onto nodeStack, making this the current node)
+void TagTree::pushNode(const std::string key)
+{
+ //TODO: adding fromArgs("offset:%s",LFA_Seek( file, offset_CD ,SEEK_SET )); <== requires file to be passed in
+ setKeyValue(key,"","");
+ //_and_ push reference to that one on stack
+ Node* pCurNode=*nodeStack.rbegin();
+ nodeStack.push_back( &*pCurNode->children.rbegin() );
+
+ if ( verbose )
+ Log::info( "pushing %d: %s",nodeStack.size(), key.c_str() );
+}
+
+//formatstring wrapper
+void TagTree::pushNode(const char* format, ...)
+{
+ char buffer[XMPQE_BUFFERSIZE];
+ va_list args;
+ va_start(args, format);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ pushNode( std::string(buffer) );
+}
+
+void TagTree::addOffset(LFA_FileRef file)
+{
+ // 3 points for doing it here: shortness, convenience, 64bit forks needed
+ #if WIN_ENV
+ addComment( "offset: 0x%I64X", LFA_Tell( file ) );
+ #elif ANDROID_ENV
+ addComment( "offset: 0x%llX", LFA_Tell( file ) );
+ #else
+ addComment( "offset: 0x%ll.16X", LFA_Tell( file ) );
+ #endif
+}
+
+//takes a Node off the stack
+// - addTag()'s will now go to the prior Node
+void TagTree::popNode()
+{
+ // FOR DEBUGGING Log::info( "pop %d",nodeStack.size() );
+
+ if (nodeStack.size() <= 1)
+ Log::error("nodeStack underflow: %d",nodeStack.size());
+ nodeStack.pop_back();
+}
+
+// takes all Nodes from the stack
+// - the right thing to do after a parsing failure to at least dump
+// the partial tree till there
+void TagTree::popAllNodes()
+{
+ while (nodeStack.size() > 1)
+ nodeStack.pop_back();
+}
+
+
+void TagTree::changeValue(const std::string value)
+{
+ if (!lastNode)
+ Log::error("lastnode NULL");
+ lastNode->value=value;
+}
+
+void TagTree::changeValue(const char* format, ...)
+{
+ char buffer[XMPQE_BUFFERSIZE];
+ va_list args;
+ va_start(args, format);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ changeValue( std::string(buffer) );
+}
+
+
+void TagTree::addComment(const std::string _comment)
+{
+ if (!lastNode)
+ Log::error("lastnode NULL");
+ lastNode->comment=lastNode->comment +
+ ( (lastNode->comment.size())?",":"") //only add comma, if there already is...
+ + _comment;
+
+ if ( verbose )
+ Log::info( " addComment: %s", _comment.c_str() );
+}
+
+void TagTree::addComment(const char* format, ...)
+{
+ char buffer[4096];
+ va_list args;
+ va_start(args, format);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ addComment( std::string(buffer) );
+}
+
+// ============================================================================
+// Output functions
+// ============================================================================
+
+// this is essentiall the last function to call...
+// - fileDump "end-users" should call it by just saying dumpTree()
+// - dumps tree to stdout (by way of Log::info)
+// - recursive calls, depth is used for indentation
+void TagTree::dumpTree(bool commentsFlag /*true*/,Node* pNode /*null*/ ,unsigned int depth /*=0*/)
+{
+ if (!pNode) { //NULL --> initial node is rootNode
+ //check (only needed on first==outermost call) that push and pop match...
+ if (nodeStack.size()>1)
+ Log::error("nodeStack not emptied: should be 1 but is %d",nodeStack.size());
+ //no need to iterate or dump rootnode itself,
+ for( NodeList::iterator iter = rootNode.children.begin(); iter != rootNode.children.end(); iter++ )
+ dumpTree ( commentsFlag,&*iter, depth); //to not iterate on first level
+ return; //...and then finally return
+ }
+
+ std::string indent(depth*2,' '); //read: tab 4
+
+ if (commentsFlag) {
+ Log::info( "%s%s%s%s%s%s%s%s%s%s", //fancy formatting foo='bar' [re,do]
+ indent.c_str(),
+ pNode->key.c_str(),
+ pNode->value.size()?" = '":"",
+ pNode->value.c_str(),
+ pNode->value.size()?"'":"",
+ ( pNode->key.size() + pNode->value.size() > 0 ) ? " ":"",
+ // standalong comments don't need extra indentation:
+ pNode->comment.size() && ( pNode->key.size() || pNode->value.size() )?
+ ((std::string("\n ")+indent).c_str()) : "",
+ pNode->comment.size() && ( pNode->key.size() || pNode->value.size() ) ?"[":"",
+ //standalone comments don't need brackets
+ pNode->comment.c_str(),
+ pNode->comment.size() && ( pNode->key.size() || pNode->value.size() ) ?"]":""
+ );
+ } else {
+ //if "NoComments" mode, then make sure, there at least is a comment to avoid blankline
+ if ( pNode->key.size() || pNode->value.size())
+ {
+ Log::info( "%s%s%s%s%s", //fancy formatting foo='bar' [re,do]
+ indent.c_str(),
+ pNode->key.c_str(),
+ pNode->value.size()?" = '":"",
+ pNode->value.c_str(),
+ pNode->value.size()?"'":""
+ );
+ }
+ }
+
+ //iterate over children (gracefully covers no-children case)
+ for( NodeList::iterator iter = pNode->children.begin(); iter != pNode->children.end(); iter++ )
+ dumpTree ( commentsFlag, &*iter, depth+1);
+}
+
+void TagTree::dumpTagMap()
+{
+ for( TagMap::iterator iter = tagMap.begin(); iter != tagMap.end(); iter++ )
+ {
+ Node* pNode=((*iter).second);
+ if (!pNode->children.size()) //supress node with children
+ Log::info( "%s%s%s%s", //(no index string this time)
+ pNode->key.c_str(),
+ pNode->value.size()?" = '":"",
+ pNode->value.c_str(),
+ pNode->value.size()?"'":""
+ );
+ }
+}
+
+// shrinked copy of dumpTree (which has faithfull order)
+// just no commenting, indenting,...)
+void TagTree::dumpTagList(Node* pNode,unsigned int depth /*=0*/)
+{
+ if (!pNode) { //NULL --> initial node is rootNode
+ //check (only needed on first==outermost call) that push and pop match...
+ if (nodeStack.size()>1)
+ Log::error("nodeStack not emptied: should be 1 but is %d",nodeStack.size());
+ //no need to iterate or dump rootnode itself,
+ for( NodeList::iterator iter = rootNode.children.begin(); iter != rootNode.children.end(); iter++ )
+ dumpTagList ( &*iter, depth);
+ return;
+ }
+
+ //make sure, there at least is a comment to avoid blankline
+ if ( pNode->key.size() || pNode->value.size())
+ {
+ Log::info( "%s%s%s%s", //fancy formatting foo='bar' [re,do]
+ pNode->key.c_str(),
+ pNode->value.size()?" = '":"",
+ pNode->value.c_str(),
+ pNode->value.size()?"'":""
+ );
+ }
+
+ for( NodeList::iterator iter = pNode->children.begin(); iter != pNode->children.end(); iter++ )
+ dumpTagList ( &*iter, depth+1);
+
+}
+
+std::string TagTree::getValue(const std::string key)
+{
+ if ( !hasNode(key) )
+ Log::error("key %s does not exist",key.c_str());
+ return tagMap[key]->value;
+}
+
+std::string TagTree::getComment(const std::string key)
+{
+ if ( !hasNode(key) )
+ Log::error("key %s does not exist",key.c_str());
+ return tagMap[key]->comment;
+}
+
+unsigned int TagTree::getSubNodePos( const std::string nodeKey, const std::string parentKey, int skip )
+{
+ Node* parent = NULL;
+
+ if( parentKey.empty() )
+ {
+ parent = &rootNode.children.front();
+ }
+ else
+ {
+ if ( ! hasNode( parentKey ) )
+ Log::error( "parent key %s does not exist", parentKey.c_str() );
+
+ parent = tagMap[parentKey];
+ }
+
+ unsigned int pos = 1;
+ NodeListIter iter;
+ for( iter = parent->children.begin();
+ iter != parent->children.end() && ( !( (iter->key == (parentKey.empty() ? nodeKey : parentKey + nodeKey)) && skip--<=0 )) ;
+ iter++, pos++ );
+
+ if( iter == parent->children.end() )
+ pos = 0;
+
+ return pos;
+}
+
+
+XMP_Int64 TagTree::getNodeSize( const std::string nodeKey )
+{
+ string tmp = tagMap[nodeKey]->comment;
+ size_t startpos = tmp.find( "size" ) + 5;
+ if( startpos == string::npos )
+ return 0;
+ tmp = tmp.substr( startpos, tmp.find_first_of( ",", startpos ) );
+
+ return strtol( tmp.c_str(), NULL, 0 );
+}
+
+
+bool TagTree::hasNode(const std::string key)
+{
+ if ( tagMap.count(key)==0 )
+ return false; //no such node
+ return true;
+}
+
+XMP_Int32 TagTree::getNodeCount(const std::string key)
+{
+ int count=1;
+ std::string extkey=key;
+ while( tagMap.count(extkey) )
+ {
+ count++;
+ extkey = key + "-" + itos(count);
+ }
+ return count-1;
+}
diff --git a/samples/source/common/TagTree.h b/samples/source/common/TagTree.h
new file mode 100644
index 0000000..9bd46d3
--- /dev/null
+++ b/samples/source/common/TagTree.h
@@ -0,0 +1,203 @@
+// =================================================================================================
+// Copyright 2008 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+
+#ifndef TagTree_H
+#define TagTree_H
+
+#include <map>
+#include <list>
+#include <string>
+
+#include "source/EndianUtils.hpp"
+#include "public/include/XMP_Const.h" //needed for setKV convenience functions
+
+#include "samples/source/common/globals.h"
+#include "samples/source/common/Log.h"
+#include "samples/source/common/LargeFileAccess.hpp"
+void LFA_Throw ( const char* msg, int id );
+
+using namespace std;
+
+// TagTree routines must be able to throw these..
+class DumpFileException : public std::runtime_error {
+ public:
+ DumpFileException(const char* format, ...);
+
+ const char* what_dumpfile_reason(); //no override, but almost..
+ private:
+ static const unsigned int DUMPFILE_MAX_ERROR_LENGTH=2048;
+ char buffer [DUMPFILE_MAX_ERROR_LENGTH+1];
+};
+
+class TagTree {
+private:
+ struct Node; //forward-looking declaration
+ typedef std::list<Node> NodeList;
+ typedef std::list<Node>::iterator NodeListIter;
+
+ struct Node {
+ std::string key; // node structure name resp. tag name
+ std::string value; // value if applicable/of relevance
+ std::string comment; // comment string, add-on as many as you like
+ NodeList children; // children of this tag, if any
+ public:
+ //default-constructor is an std::container requirement. DO NOT ACTUALLY USE.
+ Node() {
+ key=std::string("unnamed"); // should be overridden at all times
+ value=std::string("no value");
+ comment=std::string("no comment");
+ children.clear(); // be safe (std::mac-issue..) clear beforehand
+ }
+
+ //the one to use
+ Node(std::string _key, std::string _value, std::string _comment) {
+ this->key = _key;
+ this->value = _value;
+ this->comment = _comment;
+ children.clear();
+ }
+ };
+
+ // the map (always alphabetic) to collect key-value pairs
+ // - Node* rather than string to have access to value and comment info
+ typedef std::map<std::string,Node*> TagMap;
+ TagMap tagMap;
+
+ //used for changeValue and addComment
+ //(NB: not null-ed or such on push+pop, thus stretches beyond)
+ Node* lastNode;
+
+ //we need a stack to iterate in and out
+ // during build-up and dump recursion
+ typedef std::list<Node*> NodeStack;
+
+ NodeStack nodeStack;
+ Node rootNode; //TODO: ("root","");
+
+ // control verbosity to ease debugging:
+ static bool verbose;
+
+public:
+ TagTree();
+ ~TagTree();
+
+ void reset();
+
+ // verbosity control (mute by default ) ===================================
+ void setMute();
+ void setVerbose();
+
+ //input functions =========================================================
+
+ void pushNode(const std::string key);
+ void pushNode(const char* format, ...);
+
+ // add file offset as comment -> own routine to better output 64 bit offsets...
+ void addOffset(LFA_FileRef file);
+
+ void popNode();
+ void popAllNodes();
+
+ //sets a key-value pair and optinal comment. value is also optional and may be set at a later time
+ //can also be used to set pure, standalone comments (using key==value=="")
+ void setKeyValue(const std::string key,const std::string value="", const std::string comment="");
+
+ //updates the value of key without creating new key, value pairs.
+ void updateKeyValue ( const std::string key, const std::string value, const std::string comment = "" );
+
+ // convenience functions //////////////////////////////////////////////////////////////////
+ // these functions read bytes (assert in file-length), dump them to screen (as hex or as number)
+ // and optionally return the values for further processing
+
+ //read, convert endianess, dump certain values all in one go:
+ // * key - may be NULL if you just want to obtain the values but not make a KV entry
+ // * returnValue - returns the bytes digested
+ // * numOfBytes - 0-byte requests *are* legitimate, as they may reduce codeforks for the client
+ void digest(LFA_FileRef file,const std::string key="",
+ void* returnValue=NULL,
+ XMP_Int32 numOfBytes=0);
+
+ ////////////////////////////////////////////////////////////////////////////////////
+ // numeric digest routines
+ //
+ // same parameters as above, plus:
+ // * bigEndian - set to false, if the number is in the file as little endian
+ // ( correct return value according to machine type is taken care of for either setting)
+ // overload signed and unsigned, 32 and 16 bit
+ // Note, again: key may be NULL if you just want to obtain the values but not make a KV entry
+ XMP_Int64 digest64s(LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ XMP_Uns64 digest64u(LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+ XMP_Int32 digest32s(LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ XMP_Uns32 digest32u(LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+ XMP_Int16 digest16s(LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ XMP_Uns16 digest16u(LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+
+ // "expected" Overrides
+ void digest64s(XMP_Int64 expected, LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ void digest64u(XMP_Uns64 expected, LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+ void digest32s(XMP_Int32 expected, LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ void digest32u(XMP_Uns32 expected, LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+ void digest16s(XMP_Int16 expected, LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ void digest16u(XMP_Uns16 expected, LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+
+ //CBR Overrides
+ void digest64s(XMP_Int64* returnValue, LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ void digest64u(XMP_Uns64* returnValue, LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+ void digest32s(XMP_Int32* returnValue, LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ void digest32u(XMP_Uns32* returnValue, LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+ void digest16s(XMP_Int16* returnValue, LFA_FileRef file,const std::string key="", bool BigEndian=false);
+ void digest16u(XMP_Uns16* returnValue, LFA_FileRef file,const std::string key="", bool BigEndian=false,bool hexDisplay=false);
+
+
+ //8-bit string (whichever encoding -> "buginese") to std::string
+ //use length==0 to indicate zero-termination,
+ //otherwise indicated lenght will be grabbed also accross \0 's
+ //(length is counted w/o trailing zero termination, i.e. length("hans")==4 )
+ // TODO: length default = 0 not yet implemented
+ // verifyZeroTerm
+ // - has an effect only if a length!=0 is given (otherwise things go up to the 0 anyway)
+ // - in this case, asserts that the string has a terminating zero
+ // (_after_ <length> bytes, otherwise that byte is not read which has an impact on the filepointer!)
+ // would throw if any zero (\0) is encountered prior to that
+ std::string digestString(LFA_FileRef file,const std::string key="", size_t length=0, bool verifyZeroTerm=false, bool allowEarlyZeroTerm=false );
+
+ // (wrappers)
+ // standalone comment
+ void comment(const std::string comment);
+ // standalone comment
+ // be aware of bug1741056, feeding 64bit numbers might not output correctly
+ void comment(const char* format, ...);
+
+ //sometimes its worth changing (or actually setting for the first time) they current
+ //(aka last set) key/value at a later time. includes correction in tagmap.
+ void changeValue(const std::string value);
+ void changeValue(const char* format, ...);
+
+ //adds a comment to last prior entry ( which could be KeyValue, Node or standalone comment...)
+ void addComment(const std::string comment);
+ //adds a comment to last prior entry ( which could be KeyValue, Node or standalone comment...)
+ void addComment(const char* format, ...);
+
+ //output functions ===========================================================
+ void dumpTree(bool commentsFlag=true, Node* pNode = NULL,unsigned int depth=0);
+
+ void dumpTagMap();
+ void dumpTagList(Node* pNode = NULL,unsigned int depth=0);
+
+ std::string getValue(const std::string key);
+ std::string getComment(const std::string key);
+ unsigned int getSubNodePos( const std::string nodeKey, const std::string parentKey = "", int skip = 0 );
+ XMP_Int64 getNodeSize( const std::string nodeKey );
+
+ //returns true if there is such a node, false if not, error if it happens to be key-value pair
+ bool hasNode(const std::string key);
+ XMP_Int32 getNodeCount(const std::string key);
+};
+
+#endif
diff --git a/samples/source/common/XMPScanner.cpp b/samples/source/common/XMPScanner.cpp
new file mode 100644
index 0000000..5678571
--- /dev/null
+++ b/samples/source/common/XMPScanner.cpp
@@ -0,0 +1,1439 @@
+// =================================================================================================
+// Copyright 2004 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of
+// one format in a file with a different format', inventors: Sean Parent, Greg Gilley.
+// =================================================================================================
+
+#if WIN32
+ #pragma warning ( disable : 4127 ) // conditional expression is constant
+ #pragma warning ( disable : 4510 ) // default constructor could not be generated
+ #pragma warning ( disable : 4610 ) // user defined constructor required
+ #pragma warning ( disable : 4786 ) // debugger can't handle long symbol names
+#endif
+
+#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
+#include "public/include/XMP_Const.h"
+#include "samples/source/common/XMPScanner.hpp"
+#include <string>
+
+#include <cassert>
+#include <cstring>
+#include <cstdlib>
+
+#if DEBUG
+ #include <iostream>
+ #include <iomanip>
+ #include <fstream>
+#endif
+
+#ifndef UseStringPushBack // VC++ 6.x does not provide push_back for strings!
+ #define UseStringPushBack 0
+#endif
+
+using namespace std;
+
+// =================================================================================================
+// =================================================================================================
+// class PacketMachine
+// ===================
+//
+// This is the packet recognizer state machine. The top of the machine is FindNextPacket, this
+// calls the specific state components and handles transitions. The states are described by an
+// array of RecognizerInfo records, indexed by the RecognizerKind enumeration. Each RecognizerInfo
+// record has a function that does that state's work, the success and failure transition states,
+// and a string literal that is passed to the state function. The literal lets a common MatchChar
+// or MatchString function be used in several places.
+//
+// The state functions are responsible for consuming input to recognize their particular state.
+// This includes intervening nulls for 16 and 32 bit character forms. For the simplicity, things
+// are treated as essentially little endian and the nulls are not actually checked. The opening
+// '<' is found with a byte-by-byte search, then the number of bytes per character is determined
+// by counting the following nulls. From then on, consuming a character means incrementing the
+// buffer pointer by the number of bytes per character. Thus the buffer pointer only points to
+// the "real" bytes. This also means that the pointer can go off the end of the buffer by a
+// variable amount. The amount of overrun is saved so that the pointer can be positioned at the
+// right byte to start the next buffer.
+//
+// The state functions return a TriState value, eTriYes means the pattern was found, eTriNo means
+// the pattern was definitely not found, eTriMaybe means that the end of the buffer was reached
+// while working through the pattern.
+//
+// When eTriYes is returned, the fBufferPtr data member is left pointing to the "real" byte
+// following the last actual byte. Which might not be addressable memory! This also means that
+// a state function can be entered with nothing available in the buffer. When eTriNo is returned,
+// the fBufferPtr data member is left pointing to the byte that caused the failure. The state
+// machine starts over from the failure byte.
+//
+// The state functions must preserve their internal micro-state before returning eTriMaybe, and
+// resume processing when called with the next buffer. The fPosition data member is used to denote
+// how many actual characters have been consumed. The fNullCount data member is used to denote how
+// many nulls are left before the next actual character.
+
+// =================================================================================================
+// PacketMachine
+// =============
+
+XMPScanner::PacketMachine::PacketMachine ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength ) :
+
+ // Public members
+ fPacketStart ( 0 ),
+ fPacketLength ( 0 ),
+ fBytesAttr ( -1 ),
+ fCharForm ( eChar8Bit ),
+ fAccess ( ' ' ),
+ fBogusPacket ( false ),
+
+ // Private members
+ fBufferOffset ( bufferOffset ),
+ fBufferOrigin ( (const char *) bufferOrigin ),
+ fBufferPtr ( fBufferOrigin ),
+ fBufferLimit ( fBufferOrigin + bufferLength ),
+ fRecognizer ( eLeadInRecognizer ),
+ fPosition ( 0 ),
+ fBytesPerChar ( 1 ),
+ fBufferOverrun ( 0 ),
+ fQuoteChar ( ' ' )
+
+{
+ /*
+ REVIEW NOTES : Should the buffer stuff be in a class?
+ */
+
+ assert ( bufferOrigin != NULL );
+ assert ( bufferLength != 0 );
+
+} // PacketMachine
+
+// =================================================================================================
+// ~PacketMachine
+// ==============
+
+XMPScanner::PacketMachine::~PacketMachine ()
+{
+
+ // An empty placeholder.
+
+} // ~PacketMachine
+
+// =================================================================================================
+// AssociateBuffer
+// ===============
+
+void
+XMPScanner::PacketMachine::AssociateBuffer ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength )
+{
+
+ fBufferOffset = bufferOffset;
+ fBufferOrigin = (const char *) bufferOrigin;
+ fBufferPtr = fBufferOrigin + fBufferOverrun;
+ fBufferLimit = fBufferOrigin + bufferLength;
+
+} // AssociateBuffer
+
+// =================================================================================================
+// ResetMachine
+// ============
+
+void
+XMPScanner::PacketMachine::ResetMachine ()
+{
+
+ fRecognizer = eLeadInRecognizer;
+ fPosition = 0;
+ fBufferOverrun = 0;
+ fCharForm = eChar8Bit;
+ fBytesPerChar = 1;
+ fAccess = ' ';
+ fBytesAttr = -1;
+ fBogusPacket = false;
+
+ fAttrName.erase ( fAttrName.begin(), fAttrName.end() );
+ fAttrValue.erase ( fAttrValue.begin(), fAttrValue.end() );
+ fEncodingAttr.erase ( fEncodingAttr.begin(), fEncodingAttr.end() );
+
+} // ResetMachine
+
+// =================================================================================================
+// FindLessThan
+// ============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::FindLessThan ( PacketMachine * ths, const char * which )
+{
+
+ if ( *which == 'H' ) {
+
+ // --------------------------------------------------------------------------------
+ // We're looking for the '<' of the header. If we fail there is no packet in this
+ // part of the input, so return eTriNo.
+
+ ths->fCharForm = eChar8Bit; // We might have just failed from a bogus 16 or 32 bit case.
+ ths->fBytesPerChar = 1;
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) { // Don't skip nulls for the header's '<'!
+ if ( *ths->fBufferPtr == '<' ) break;
+ ths->fBufferPtr++;
+ }
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriNo;
+ ths->fBufferPtr++;
+ return eTriYes;
+
+ } else {
+
+ // --------------------------------------------------------------------------------
+ // We're looking for the '<' of the trailer. We're already inside the packet body,
+ // looking for the trailer. So here if we fail we must return eTriMaybe so that we
+ // keep looking for the trailer in the next buffer.
+
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) {
+ if ( *ths->fBufferPtr == '<' ) break;
+ ths->fBufferPtr += bytesPerChar;
+ }
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+
+ }
+
+} // FindLessThan
+
+// =================================================================================================
+// MatchString
+// ===========
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchString ( PacketMachine * ths, const char * literal )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+ const char * litPtr = literal + ths->fPosition;
+ const XMP_Int32 charsToGo = (XMP_Int32) strlen ( literal ) - ths->fPosition;
+ int charsDone = 0;
+
+ while ( (charsDone < charsToGo) && (ths->fBufferPtr < ths->fBufferLimit) ) {
+ if ( *litPtr != *ths->fBufferPtr ) return eTriNo;
+ charsDone++;
+ litPtr++;
+ ths->fBufferPtr += bytesPerChar;
+ }
+
+ if ( charsDone == charsToGo ) return eTriYes;
+ ths->fPosition += charsDone;
+ return eTriMaybe;
+
+} // MatchString
+
+// =================================================================================================
+// MatchChar
+// =========
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchChar ( PacketMachine * ths, const char * literal )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+ if ( currChar != *literal ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+
+} // MatchChar
+
+// =================================================================================================
+// MatchOpenQuote
+// ==============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchOpenQuote ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+ if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
+ ths->fQuoteChar = currChar;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+
+} // MatchOpenQuote
+
+// =================================================================================================
+// MatchCloseQuote
+// ===============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::MatchCloseQuote ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ return MatchChar ( ths, &ths->fQuoteChar );
+
+} // MatchCloseQuote
+
+// =================================================================================================
+// CaptureAttrName
+// ===============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CaptureAttrName ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+ char currChar;
+
+ if ( ths->fPosition == 0 ) { // Get the first character in the name.
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ currChar = *ths->fBufferPtr;
+ if ( ths->fAttrName.size() == 0 ) {
+ if ( ! ( ( ('a' <= currChar) && (currChar <= 'z') ) ||
+ ( ('A' <= currChar) && (currChar <= 'Z') ) ||
+ (currChar == '_') || (currChar == ':') ) ) {
+ return eTriNo;
+ }
+ }
+
+ ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
+ #if UseStringPushBack
+ ths->fAttrName.push_back ( currChar );
+ #else
+ ths->fAttrName.insert ( ths->fAttrName.end(), currChar );
+ #endif
+ ths->fBufferPtr += bytesPerChar;
+
+ }
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) { // Get the remainder of the name.
+
+ currChar = *ths->fBufferPtr;
+ if ( ! ( ( ('a' <= currChar) && (currChar <= 'z') ) ||
+ ( ('A' <= currChar) && (currChar <= 'Z') ) ||
+ ( ('0' <= currChar) && (currChar <= '9') ) ||
+ (currChar == '-') || (currChar == '.') || (currChar == '_') || (currChar == ':') ) ) {
+ break;
+ }
+
+ #if UseStringPushBack
+ ths->fAttrName.push_back ( currChar );
+ #else
+ ths->fAttrName.insert ( ths->fAttrName.end(), currChar );
+ #endif
+ ths->fBufferPtr += bytesPerChar;
+
+ }
+
+ if ( ths->fBufferPtr < ths->fBufferLimit ) return eTriYes;
+ ths->fPosition = (long) ths->fAttrName.size(); // The name might span into the next buffer.
+ return eTriMaybe;
+
+} // CaptureAttrName
+
+// =================================================================================================
+// CaptureAttrValue
+// ================
+//
+// Recognize the equal sign and the quoted string value, capture the value along the way.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CaptureAttrValue ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+ char currChar = 0;
+ TriState result = eTriMaybe;
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // The name should haved ended at the '=', nulls already skipped.
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+ if ( *ths->fBufferPtr != '=' ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar;
+ ths->fPosition = 1;
+ // fall through OK because MatchOpenQuote will check the buffer limit and nulls ...
+
+ case 1 : // Look for the open quote.
+
+ result = MatchOpenQuote ( ths, NULL );
+ if ( result != eTriYes ) return result;
+ ths->fPosition = 2;
+ // fall through OK because the buffer limit and nulls are checked below ...
+
+ default : // Look for the close quote, capturing the value along the way.
+
+ assert ( ths->fPosition == 2 );
+
+ const char quoteChar = ths->fQuoteChar;
+
+ while ( ths->fBufferPtr < ths->fBufferLimit ) {
+ currChar = *ths->fBufferPtr;
+ if ( currChar == quoteChar ) break;
+ #if UseStringPushBack
+ ths->fAttrValue.push_back ( currChar );
+ #else
+ ths->fAttrValue.insert ( ths->fAttrValue.end(), currChar );
+ #endif
+ ths->fBufferPtr += bytesPerChar;
+ }
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+ assert ( currChar == quoteChar );
+ ths->fBufferPtr += bytesPerChar; // Advance past the closing quote.
+ return eTriYes;
+
+ }
+
+} // CaptureAttrValue
+
+// =================================================================================================
+// RecordStart
+// ===========
+//
+// Note that this routine looks at bytes, not logical characters. It has to figure out how many
+// bytes per character there are so that the other recognizers can skip intervening nulls.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecordStart ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ while ( true ) {
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currByte = *ths->fBufferPtr;
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // Record the length.
+ assert ( ths->fCharForm == eChar8Bit );
+ assert ( ths->fBytesPerChar == 1 );
+ ths->fPacketStart = ths->fBufferOffset + ((ths->fBufferPtr - 1) - ths->fBufferOrigin);
+ ths->fPacketLength = 0;
+ ths->fPosition = 1;
+ // ! OK to fall through here, we didn't consume a byte in this step.
+
+ case 1 : // Look for the first null byte.
+ if ( currByte != 0 ) return eTriYes; // No nulls found.
+ ths->fCharForm = eChar16BitBig; // Assume 16 bit big endian for now.
+ ths->fBytesPerChar = 2;
+ ths->fBufferPtr++;
+ ths->fPosition = 2;
+ break; // ! Don't fall through, have to check for the end of the buffer between each byte.
+
+ case 2 : // One null was found, look for a second.
+ if ( currByte != 0 ) return eTriYes; // Just one null found.
+ ths->fBufferPtr++;
+ ths->fPosition = 3;
+ break;
+
+ case 3 : // Two nulls were found, look for a third.
+ if ( currByte != 0 ) return eTriNo; // Just two nulls is not valid.
+ ths->fCharForm = eChar32BitBig; // Assume 32 bit big endian for now.
+ ths->fBytesPerChar = 4;
+ ths->fBufferPtr++;
+ return eTriYes;
+ break;
+
+ }
+
+ }
+
+} // RecordStart
+
+// =================================================================================================
+// RecognizeBOM
+// ============
+//
+// Recognizing the byte order marker is a surprisingly messy thing to do. It can't be done by the
+// normal string matcher, there are no intervening nulls. There are 4 transitions after the opening
+// quote, the closing quote or one of the three encodings. For the actual BOM there are then 1 or 2
+// following bytes that depend on which of the encodings we're in. Not to mention that the buffer
+// might end at any point.
+//
+// The intervening null count done earlier determined 8, 16, or 32 bits per character, but not the
+// big or little endian nature for the 16/32 bit cases. The BOM must be present for the 16 and 32
+// bit cases in order to determine the endian mode. There are six possible byte sequences for the
+// quoted BOM string, ignoring the differences for quoting with ''' versus '"'.
+//
+// Keep in mind that for the 16 and 32 bit cases there will be nulls for the quote. In the table
+// below the symbol <quote> means just the one byte containing the ''' or '"'. The nulls for the
+// quote character are explicitly shown.
+//
+// <quote> <quote> - 1: No BOM, this must be an 8 bit case.
+// <quote> \xEF \xBB \xBF <quote> - 1.12-13: The 8 bit form.
+//
+// <quote> \xFE \xFF \x00 <quote> - 1.22-23: The 16 bit, big endian form
+// <quote> \x00 \xFF \xFE <quote> - 1.32-33: The 16 bit, little endian form.
+//
+// <quote> \x00 \x00 \xFE \xFF \x00 \x00 \x00 <quote> - 1.32.43-45.56-57: The 32 bit, big endian form.
+// <quote> \x00 \x00 \x00 \xFF \xFE \x00 \x00 <quote> - 1.32.43.54-57: The 32 bit, little endian form.
+
+enum {
+ eBOM_8_1 = 0xEF,
+ eBOM_8_2 = 0xBB,
+ eBOM_8_3 = 0xBF,
+ eBOM_Big_1 = 0xFE,
+ eBOM_Big_2 = 0xFF,
+ eBOM_Little_1 = eBOM_Big_2,
+ eBOM_Little_2 = eBOM_Big_1
+};
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecognizeBOM ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ while ( true ) { // Handle one character at a time, the micro-state (fPosition) changes for each.
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const unsigned char currChar = *ths->fBufferPtr; // ! The BOM bytes look like integers bigger than 127.
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // Look for the opening quote.
+ if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
+ ths->fQuoteChar = currChar;
+ ths->fBufferPtr++;
+ ths->fPosition = 1;
+ break; // ! Don't fall through, have to check for the end of the buffer between each byte.
+
+ case 1 : // Look at the byte immediately following the opening quote.
+ if ( currChar == ths->fQuoteChar ) { // Closing quote, no BOM character, must be 8 bit.
+ if ( ths->fCharForm != eChar8Bit ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar; // Skip the nulls after the closing quote.
+ return eTriYes;
+ } else if ( currChar == eBOM_8_1 ) { // Start of the 8 bit form.
+ if ( ths->fCharForm != eChar8Bit ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 12;
+ } else if ( currChar == eBOM_Big_1 ) { // Start of the 16 bit big endian form.
+ if ( ths->fCharForm != eChar16BitBig ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 22;
+ } else if ( currChar == 0 ) { // Start of the 16 bit little endian or either 32 bit form.
+ if ( ths->fCharForm == eChar8Bit ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 32;
+ } else {
+ return eTriNo;
+ }
+ break;
+
+ case 12 : // Look for the second byte of the 8 bit form.
+ if ( currChar != eBOM_8_2 ) return eTriNo;
+ ths->fPosition = 13;
+ ths->fBufferPtr++;
+ break;
+
+ case 13 : // Look for the third byte of the 8 bit form.
+ if ( currChar != eBOM_8_3 ) return eTriNo;
+ ths->fPosition = 99;
+ ths->fBufferPtr++;
+ break;
+
+ case 22 : // Look for the second byte of the 16 bit big endian form.
+ if ( currChar != eBOM_Big_2 ) return eTriNo;
+ ths->fPosition = 23;
+ ths->fBufferPtr++;
+ break;
+
+ case 23 : // Look for the null before the closing quote of the 16 bit big endian form.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fBufferPtr++;
+ ths->fPosition = 99;
+ break;
+
+ case 32 : // Look at the second byte of the 16 bit little endian or either 32 bit form.
+ if ( currChar == eBOM_Little_1 ) {
+ ths->fPosition = 33;
+ } else if ( currChar == 0 ) {
+ ths->fPosition = 43;
+ } else {
+ return eTriNo;
+ }
+ ths->fBufferPtr++;
+ break;
+
+ case 33 : // Look for the third byte of the 16 bit little endian form.
+ if ( ths->fCharForm != eChar16BitBig ) return eTriNo; // Null count before assumed big endian.
+ if ( currChar != eBOM_Little_2 ) return eTriNo;
+ ths->fCharForm = eChar16BitLittle;
+ ths->fPosition = 99;
+ ths->fBufferPtr++;
+ break;
+
+ case 43 : // Look at the third byte of either 32 bit form.
+ if ( ths->fCharForm != eChar32BitBig ) return eTriNo; // Null count before assumed big endian.
+ if ( currChar == eBOM_Big_1 ) {
+ ths->fPosition = 44;
+ } else if ( currChar == 0 ) {
+ ths->fPosition = 54;
+ } else {
+ return eTriNo;
+ }
+ ths->fBufferPtr++;
+ break;
+
+ case 44 : // Look for the fourth byte of the 32 bit big endian form.
+ if ( currChar != eBOM_Big_2 ) return eTriNo;
+ ths->fPosition = 45;
+ ths->fBufferPtr++;
+ break;
+
+ case 45 : // Look for the first null before the closing quote of the 32 bit big endian form.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fPosition = 56;
+ ths->fBufferPtr++;
+ break;
+
+ case 54 : // Look for the fourth byte of the 32 bit little endian form.
+ ths->fCharForm = eChar32BitLittle;
+ if ( currChar != eBOM_Little_1 ) return eTriNo;
+ ths->fPosition = 55;
+ ths->fBufferPtr++;
+ break;
+
+ case 55 : // Look for the fifth byte of the 32 bit little endian form.
+ if ( currChar != eBOM_Little_2 ) return eTriNo;
+ ths->fPosition = 56;
+ ths->fBufferPtr++;
+ break;
+
+ case 56 : // Look for the next to last null before the closing quote of the 32 bit forms.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fPosition = 57;
+ ths->fBufferPtr++;
+ break;
+
+ case 57 : // Look for the last null before the closing quote of the 32 bit forms.
+ if ( currChar != 0 ) return eTriNo;
+ ths->fPosition = 99;
+ ths->fBufferPtr++;
+ break;
+
+ default : // Look for the closing quote.
+ assert ( ths->fPosition == 99 );
+ if ( currChar != ths->fQuoteChar ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar; // Skip the nulls after the closing quote.
+ return eTriYes;
+ break;
+
+ }
+
+ }
+
+} // RecognizeBOM
+
+// =================================================================================================
+// RecordHeadAttr
+// ==============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecordHeadAttr ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ if ( ths->fAttrName == "encoding" ) {
+
+ assert ( ths->fEncodingAttr.empty() );
+ ths->fEncodingAttr = ths->fAttrValue;
+
+ } else if ( ths->fAttrName == "bytes" ) {
+
+ long value = 0;
+ int count = (int) ths->fAttrValue.size();
+ int i;
+
+ assert ( ths->fBytesAttr == -1 );
+
+ if ( count > 0 ) { // Allow bytes='' to be the same as no bytes attribute.
+
+ for ( i = 0; i < count; i++ ) {
+ const char currChar = ths->fAttrValue[i];
+ if ( ('0' <= currChar) && (currChar <= '9') ) {
+ value = (value * 10) + (currChar - '0');
+ } else {
+ ths->fBogusPacket = true;
+ value = -1;
+ break;
+ }
+ }
+ ths->fBytesAttr = value;
+
+ if ( CharFormIs16Bit ( ths->fCharForm ) ) {
+ if ( (ths->fBytesAttr & 1) != 0 ) ths->fBogusPacket = true;
+ } else if ( CharFormIs32Bit ( ths->fCharForm ) ) {
+ if ( (ths->fBytesAttr & 3) != 0 ) ths->fBogusPacket = true;
+ }
+
+ }
+
+ }
+
+ ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
+ ths->fAttrValue.erase ( ths->fAttrValue.begin(), ths->fAttrValue.end() );
+
+ return eTriYes;
+
+} // RecordHeadAttr
+
+// =================================================================================================
+// CaptureAccess
+// =============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CaptureAccess ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ while ( true ) {
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+
+ switch ( ths->fPosition ) {
+
+ case 0 : // Look for the opening quote.
+ if ( (currChar != '\'') && (currChar != '"') ) return eTriNo;
+ ths->fQuoteChar = currChar;
+ ths->fBufferPtr += bytesPerChar;
+ ths->fPosition = 1;
+ break; // ! Don't fall through, have to check for the end of the buffer between each byte.
+
+ case 1 : // Look for the 'r' or 'w'.
+ if ( (currChar != 'r') && (currChar != 'w') ) return eTriNo;
+ ths->fAccess = currChar;
+ ths->fBufferPtr += bytesPerChar;
+ ths->fPosition = 2;
+ break;
+
+ default : // Look for the closing quote.
+ assert ( ths->fPosition == 2 );
+ if ( currChar != ths->fQuoteChar ) return eTriNo;
+ ths->fBufferPtr += bytesPerChar;
+ return eTriYes;
+ break;
+
+ }
+
+ }
+
+} // CaptureAccess
+
+// =================================================================================================
+// RecordTailAttr
+// ==============
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::RecordTailAttr ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ // There are no known "general" attributes for the packet trailer.
+
+ ths->fAttrName.erase ( ths->fAttrName.begin(), ths->fAttrName.end() );
+ ths->fAttrValue.erase ( ths->fAttrValue.begin(), ths->fAttrValue.end() );
+
+ return eTriYes;
+
+} // RecordTailAttr
+
+// =================================================================================================
+// CheckPacketEnd
+// ==============
+//
+// Check for trailing padding and record the packet length. We have trailing padding if the bytes
+// attribute is present and has a value greater than the current length.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CheckPacketEnd ( PacketMachine * ths, const char * /* unused */ )
+{
+ const int bytesPerChar = ths->fBytesPerChar;
+
+ if ( ths->fPosition == 0 ) { // First call, decide if there is trailing padding.
+
+ const XMP_Int64 currLen64 = (ths->fBufferOffset + (ths->fBufferPtr - ths->fBufferOrigin)) - ths->fPacketStart;
+ if ( currLen64 > 0x7FFFFFFF ) throw std::runtime_error ( "Packet length exceeds 2GB-1" );
+ const XMP_Int32 currLength = (XMP_Int32)currLen64;
+
+ if ( (ths->fBytesAttr != -1) && (ths->fBytesAttr != currLength) ) {
+ if ( ths->fBytesAttr < currLength ) {
+ ths->fBogusPacket = true; // The bytes attribute value is too small.
+ } else {
+ ths->fPosition = ths->fBytesAttr - currLength;
+ if ( (ths->fPosition % ths->fBytesPerChar) != 0 ) {
+ ths->fBogusPacket = true; // The padding is not a multiple of the character size.
+ ths->fPosition = (ths->fPosition / ths->fBytesPerChar) * ths->fBytesPerChar;
+ }
+ }
+ }
+
+ }
+
+ while ( ths->fPosition > 0 ) {
+
+ if ( ths->fBufferPtr >= ths->fBufferLimit ) return eTriMaybe;
+
+ const char currChar = *ths->fBufferPtr;
+
+ if ( (currChar != ' ') && (currChar != '\t') && (currChar != '\n') && (currChar != '\r') ) {
+ ths->fBogusPacket = true; // The padding is not whitespace.
+ break; // Stop the packet here.
+ }
+
+ ths->fPosition -= bytesPerChar;
+ ths->fBufferPtr += bytesPerChar;
+
+ }
+
+ const XMP_Int64 currLen64 = (ths->fBufferOffset + (ths->fBufferPtr - ths->fBufferOrigin)) - ths->fPacketStart;
+ if ( currLen64 > 0x7FFFFFFF ) throw std::runtime_error ( "Packet length exceeds 2GB-1" );
+ ths->fPacketLength = (XMP_Int32)currLen64;
+ return eTriYes;
+
+} // CheckPacketEnd
+
+// =================================================================================================
+// CheckFinalNulls
+// ===============
+//
+// Do some special case processing for little endian characters. We have to make sure the presumed
+// nulls after the last character actually exist, i.e. that the stream does not end too soon. Note
+// that the prior character scanning has moved the buffer pointer to the address following the last
+// byte of the last character. I.e. we're already past the presumed nulls, so we can't check their
+// content. All we can do is verify that the stream does not end too soon.
+//
+// Doing this check is simple yet subtle. If we're still in the current buffer then the trailing
+// bytes obviously exist. If we're exactly at the end of the buffer then the bytes also exist.
+// The only question is when we're actually past this buffer, partly into the next buffer. This is
+// when "ths->fBufferPtr > ths->fBufferLimit" on entry. For that case we have to wait until we've
+// actually seen enough extra bytes of input.
+//
+// Since the normal buffer processing is already adjusting for this partial character overrun, all
+// that needs to be done here is wait until "ths->fBufferPtr <= ths->fBufferLimit" on entry. In
+// other words, if we're presently too far, ths->fBufferPtr will be adjusted by the amount of the
+// overflow the next time XMPScanner::Scan is called. This might still be too far, so just keep
+// waiting for enough data to pass by.
+//
+// Note that there is a corresponding special case for big endian characters, we must decrement the
+// starting offset by the number of leading nulls. But we don't do that here, we leave it to the
+// outer code. This is because the leading nulls might have been at the exact end of a previous
+// buffer, in which case we have to also decrement the length of that raw data snip.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::CheckFinalNulls ( PacketMachine * ths, const char * /* unused */ )
+{
+
+ if ( (ths->fCharForm != eChar8Bit) && CharFormIsLittleEndian ( ths->fCharForm ) ) {
+ if ( ths->fBufferPtr > ths->fBufferLimit ) return eTriMaybe;
+ }
+
+ return eTriYes;
+
+} // CheckFinalNulls
+
+// =================================================================================================
+// SetNextRecognizer
+// =================
+
+void
+XMPScanner::PacketMachine::SetNextRecognizer ( RecognizerKind nextRecognizer )
+{
+
+ fRecognizer = nextRecognizer;
+ fPosition = 0;
+
+} // SetNextRecognizer
+
+// =================================================================================================
+// FindNextPacket
+// ==============
+
+// *** When we start validating intervening nulls for 2 and 4 bytes characters, throw an exception
+// *** for errors. Don't return eTriNo, that might skip at an optional point.
+
+XMPScanner::PacketMachine::TriState
+XMPScanner::PacketMachine::FindNextPacket ()
+{
+
+ TriState status;
+
+ #define kPacketHead "?xpacket begin="
+ #define kPacketID "W5M0MpCehiHzreSzNTczkc9d"
+ #define kPacketTail "?xpacket end="
+
+ static const RecognizerInfo recognizerTable [eRecognizerCount] = { // ! Would be safer to assign these explicitly.
+
+ // proc successNext failureNext literal
+
+ { NULL, eFailureRecognizer, eFailureRecognizer, NULL}, // eFailureRecognizer
+ { NULL, eSuccessRecognizer, eSuccessRecognizer, NULL}, // eSuccessRecognizer
+
+ { FindLessThan, eHeadStartRecorder, eFailureRecognizer, "H" }, // eLeadInRecognizer
+ { RecordStart, eHeadStartRecognizer, eLeadInRecognizer, NULL }, // eHeadStartRecorder
+ { MatchString, eBOMRecognizer, eLeadInRecognizer, kPacketHead }, // eHeadStartRecognizer
+
+ { RecognizeBOM, eIDTagRecognizer, eLeadInRecognizer, NULL }, // eBOMRecognizer
+
+ { MatchString, eIDOpenRecognizer, eLeadInRecognizer, " id=" }, // eIDTagRecognizer
+ { MatchOpenQuote, eIDValueRecognizer, eLeadInRecognizer, NULL }, // eIDOpenRecognizer
+ { MatchString, eIDCloseRecognizer, eLeadInRecognizer, kPacketID }, // eIDValueRecognizer
+ { MatchCloseQuote, eAttrSpaceRecognizer_1, eLeadInRecognizer, NULL }, // eIDCloseRecognizer
+
+ { MatchChar, eAttrNameRecognizer_1, eHeadEndRecognizer, " " }, // eAttrSpaceRecognizer_1
+ { CaptureAttrName, eAttrValueRecognizer_1, eLeadInRecognizer, NULL }, // eAttrNameRecognizer_1
+ { CaptureAttrValue, eAttrValueRecorder_1, eLeadInRecognizer, NULL }, // eAttrValueRecognizer_1
+ { RecordHeadAttr, eAttrSpaceRecognizer_1, eLeadInRecognizer, NULL }, // eAttrValueRecorder_1
+
+ { MatchString, eBodyRecognizer, eLeadInRecognizer, "?>" }, // eHeadEndRecognizer
+
+ { FindLessThan, eTailStartRecognizer, eBodyRecognizer, "T"}, // eBodyRecognizer
+
+ { MatchString, eAccessValueRecognizer, eBodyRecognizer, kPacketTail }, // eTailStartRecognizer
+ { CaptureAccess, eAttrSpaceRecognizer_2, eBodyRecognizer, NULL }, // eAccessValueRecognizer
+
+ { MatchChar, eAttrNameRecognizer_2, eTailEndRecognizer, " " }, // eAttrSpaceRecognizer_2
+ { CaptureAttrName, eAttrValueRecognizer_2, eBodyRecognizer, NULL }, // eAttrNameRecognizer_2
+ { CaptureAttrValue, eAttrValueRecorder_2, eBodyRecognizer, NULL }, // eAttrValueRecognizer_2
+ { RecordTailAttr, eAttrSpaceRecognizer_2, eBodyRecognizer, NULL }, // eAttrValueRecorder_2
+
+ { MatchString, ePacketEndRecognizer, eBodyRecognizer, "?>" }, // eTailEndRecognizer
+ { CheckPacketEnd, eCloseOutRecognizer, eBodyRecognizer, "" }, // ePacketEndRecognizer
+ { CheckFinalNulls, eSuccessRecognizer, eBodyRecognizer, "" } // eCloseOutRecognizer
+
+ };
+
+ while ( true ) {
+
+ switch ( fRecognizer ) {
+
+ case eFailureRecognizer :
+ return eTriNo;
+
+ case eSuccessRecognizer :
+ return eTriYes;
+
+ default :
+
+ // -------------------------------------------------------------------
+ // For everything else, the normal cases, use the state machine table.
+
+ const RecognizerInfo * thisState = &recognizerTable [fRecognizer];
+
+ status = thisState->proc ( this, thisState->literal );
+
+ switch ( status ) {
+
+ case eTriNo :
+ SetNextRecognizer ( thisState->failureNext );
+ continue;
+
+ case eTriYes :
+ SetNextRecognizer ( thisState->successNext );
+ continue;
+
+ case eTriMaybe :
+ fBufferOverrun = (unsigned char)(fBufferPtr - fBufferLimit);
+ return eTriMaybe; // Keep this recognizer intact, to be resumed later.
+
+ }
+
+ } // switch ( fRecognizer ) { ...
+
+ } // while ( true ) { ...
+
+} // FindNextPacket
+
+// =================================================================================================
+// =================================================================================================
+// class InternalSnip
+// ==================
+
+// =================================================================================================
+// InternalSnip
+// ============
+
+XMPScanner::InternalSnip::InternalSnip ( XMP_Int64 offset, XMP_Int64 length )
+{
+
+ fInfo.fOffset = offset;
+ fInfo.fLength = length;
+
+} // InternalSnip
+
+// =================================================================================================
+// InternalSnip
+// ============
+
+XMPScanner::InternalSnip::InternalSnip ( const InternalSnip & rhs ) :
+ fInfo ( rhs.fInfo ),
+ fMachine ( NULL )
+{
+
+ assert ( rhs.fMachine.get() == NULL ); // Don't copy a snip with a machine.
+ assert ( (rhs.fInfo.fEncodingAttr == 0) || (*rhs.fInfo.fEncodingAttr == 0) ); // Don't copy a snip with an encoding.
+
+} // InternalSnip
+
+// =================================================================================================
+// ~InternalSnip
+// =============
+
+XMPScanner::InternalSnip::~InternalSnip ()
+{
+} // ~InternalSnip
+
+
+// =================================================================================================
+// =================================================================================================
+// class XMPScanner
+// ================
+
+// =================================================================================================
+// DumpSnipList
+// ============
+
+#if DEBUG
+
+static const char * snipStateName [6] = { "not-seen", "pending", "raw-data", "good-packet", "partial", "bad-packet" };
+
+void
+XMPScanner::DumpSnipList ( const char * title )
+{
+ InternalSnipIterator currPos = fInternalSnips.begin();
+ InternalSnipIterator endPos = fInternalSnips.end();
+
+ cout << endl << title << " snip list: " << fInternalSnips.size() << endl;
+
+ for ( ; currPos != endPos; ++currPos ) {
+ SnipInfo * currSnip = &currPos->fInfo;
+ cout << '\t' << currSnip << ' ' << snipStateName[currSnip->fState] << ' '
+ << currSnip->fOffset << ".." << (currSnip->fOffset + currSnip->fLength - 1)
+ << ' ' << currSnip->fLength << ' ' << endl;
+ }
+} // DumpSnipList
+
+#endif
+
+// =================================================================================================
+// PrevSnip and NextSnip
+// =====================
+
+XMPScanner::InternalSnipIterator
+XMPScanner::PrevSnip ( InternalSnipIterator snipPos )
+{
+
+ InternalSnipIterator prev = snipPos;
+ return --prev;
+
+} // PrevSnip
+
+XMPScanner::InternalSnipIterator
+XMPScanner::NextSnip ( InternalSnipIterator snipPos )
+{
+
+ InternalSnipIterator next = snipPos;
+ return ++next;
+
+} // NextSnip
+
+// =================================================================================================
+// XMPScanner
+// ==========
+//
+// Initialize the scanner object with one "not seen" snip covering the whole stream.
+
+XMPScanner::XMPScanner ( XMP_Int64 streamLength ) :
+
+ fStreamLength ( streamLength )
+
+{
+ InternalSnip rootSnip ( 0, streamLength );
+
+ if ( streamLength > 0 ) fInternalSnips.push_front ( rootSnip ); // Be nice for empty files.
+ // DumpSnipList ( "New XMPScanner" );
+
+} // XMPScanner
+
+// =================================================================================================
+// ~XMPScanner
+// ===========
+
+XMPScanner::~XMPScanner()
+{
+
+} // ~XMPScanner
+
+// =================================================================================================
+// GetSnipCount
+// ============
+
+long
+XMPScanner::GetSnipCount ()
+{
+
+ return (long)fInternalSnips.size();
+
+} // GetSnipCount
+
+// =================================================================================================
+// StreamAllScanned
+// ================
+
+bool
+XMPScanner::StreamAllScanned ()
+{
+ InternalSnipIterator currPos = fInternalSnips.begin();
+ InternalSnipIterator endPos = fInternalSnips.end();
+
+ for ( ; currPos != endPos; ++currPos ) {
+ if ( currPos->fInfo.fState == eNotSeenSnip ) return false;
+ }
+ return true;
+
+} // StreamAllScanned
+
+// =================================================================================================
+// SplitInternalSnip
+// =================
+//
+// Split the given snip into up to 3 pieces. The new pieces are inserted before and after this one
+// in the snip list. The relOffset is the first byte to be kept, it is relative to this snip. If
+// the preceeding or following snips have the same state as this one, just shift the boundaries.
+// I.e. move the contents from one snip to the other, don't create a new snip.
+
+// *** To be thread safe we ought to lock the entire list during manipulation. Let data scanning
+// *** happen in parallel, serialize all mucking with the list.
+
+void
+XMPScanner::SplitInternalSnip ( InternalSnipIterator snipPos, XMP_Int64 relOffset, XMP_Int64 newLength )
+{
+
+ assert ( (relOffset + newLength) > relOffset ); // Check for overflow.
+ assert ( (relOffset + newLength) <= snipPos->fInfo.fLength );
+
+ // -----------------------------------
+ // First deal with the low offset end.
+
+ if ( relOffset > 0 ) {
+
+ InternalSnipIterator prevPos;
+ if ( snipPos != fInternalSnips.begin() ) prevPos = PrevSnip ( snipPos );
+
+ if ( (snipPos != fInternalSnips.begin()) && (snipPos->fInfo.fState == prevPos->fInfo.fState) ) {
+ prevPos->fInfo.fLength += relOffset; // Adjust the preceeding snip.
+ } else {
+ InternalSnip headExcess ( snipPos->fInfo.fOffset, relOffset );
+ headExcess.fInfo.fState = snipPos->fInfo.fState;
+ headExcess.fInfo.fOutOfOrder = snipPos->fInfo.fOutOfOrder;
+ fInternalSnips.insert ( snipPos, headExcess ); // Insert the head piece before the middle piece.
+ }
+
+ snipPos->fInfo.fOffset += relOffset; // Adjust the remainder of this snip.
+ snipPos->fInfo.fLength -= relOffset;
+
+ }
+
+ // ----------------------------------
+ // Now deal with the high offset end.
+
+ if ( newLength < snipPos->fInfo.fLength ) {
+
+ InternalSnipIterator nextPos = NextSnip ( snipPos );
+ const XMP_Int64 tailLength = snipPos->fInfo.fLength - newLength;
+
+ if ( (nextPos != fInternalSnips.end()) && (snipPos->fInfo.fState == nextPos->fInfo.fState) ) {
+ nextPos->fInfo.fOffset -= tailLength; // Adjust the following snip.
+ nextPos->fInfo.fLength += tailLength;
+ } else {
+ InternalSnip tailExcess ( (snipPos->fInfo.fOffset + newLength), tailLength );
+ tailExcess.fInfo.fState = snipPos->fInfo.fState;
+ tailExcess.fInfo.fOutOfOrder = snipPos->fInfo.fOutOfOrder;
+ fInternalSnips.insert ( nextPos, tailExcess ); // Insert the tail piece after the middle piece.
+ }
+
+ snipPos->fInfo.fLength = newLength;
+
+ }
+
+} // SplitInternalSnip
+
+// =================================================================================================
+// MergeInternalSnips
+// ==================
+
+XMPScanner::InternalSnipIterator
+XMPScanner::MergeInternalSnips ( InternalSnipIterator firstPos, InternalSnipIterator secondPos )
+{
+
+ firstPos->fInfo.fLength += secondPos->fInfo.fLength;
+ fInternalSnips.erase ( secondPos );
+ return firstPos;
+
+} // MergeInternalSnips
+
+// =================================================================================================
+// Scan
+// ====
+
+void
+XMPScanner::Scan ( const void * bufferOrigin, XMP_Int64 bufferOffset, XMP_Int64 bufferLength )
+{
+ XMP_Int64 relOffset;
+
+ #if 0
+ cout << "Scan: @ " << bufferOrigin << ", " << bufferOffset << ", " << bufferLength << endl;
+ #endif
+
+ if ( bufferLength == 0 ) return;
+
+ // ----------------------------------------------------------------
+ // These comparisons are carefully done to avoid overflow problems.
+
+ if ( (bufferOffset >= fStreamLength) ||
+ (bufferLength > (fStreamLength - bufferOffset)) ||
+ (bufferOrigin == 0) ) {
+ throw ScanError ( "Bad origin, offset, or length" );
+ }
+
+ // ----------------------------------------------------------------------------------------------
+ // This buffer must be within a not-seen snip. Find it and split it. The first snip whose whose
+ // end is beyond the buffer must be the enclosing one.
+
+ // *** It would be friendly for rescans for out of order problems to accept any buffer postion.
+
+ const XMP_Int64 endOffset = bufferOffset + bufferLength - 1;
+ InternalSnipIterator snipPos = fInternalSnips.begin();
+
+ while ( endOffset > (snipPos->fInfo.fOffset + snipPos->fInfo.fLength - 1) ) ++ snipPos;
+ if ( snipPos->fInfo.fState != eNotSeenSnip ) throw ScanError ( "Already seen" );
+
+ relOffset = bufferOffset - snipPos->fInfo.fOffset;
+ if ( (relOffset + bufferLength) > snipPos->fInfo.fLength ) throw ScanError ( "Not within existing snip" );
+
+ SplitInternalSnip ( snipPos, relOffset, bufferLength ); // *** If sequential & prev is partial, just tack on,
+
+ // --------------------------------------------------------
+ // Merge this snip with the preceeding snip if appropriate.
+
+ // *** When out of order I/O is supported we have to do something about buffers who's predecessor is not seen.
+
+ if ( snipPos->fInfo.fOffset > 0 ) {
+ InternalSnipIterator prevPos = PrevSnip ( snipPos );
+ if ( prevPos->fInfo.fState == ePartialPacketSnip ) snipPos = MergeInternalSnips ( prevPos, snipPos );
+ }
+
+ // ----------------------------------
+ // Look for packets within this snip.
+
+ snipPos->fInfo.fState = ePendingSnip;
+ PacketMachine* thisMachine = snipPos->fMachine.get();
+ // DumpSnipList ( "Before scan" );
+
+ if ( thisMachine != 0 ) {
+ thisMachine->AssociateBuffer ( bufferOffset, bufferOrigin, bufferLength );
+ } else {
+ // *** snipPos->fMachine.reset ( new PacketMachine ( bufferOffset, bufferOrigin, bufferLength ) ); VC++ lacks reset
+ #if 0
+ snipPos->fMachine = auto_ptr<PacketMachine> ( new PacketMachine ( bufferOffset, bufferOrigin, bufferLength ) );
+ #else
+ {
+ // Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
+ PacketMachine * pm = new PacketMachine ( bufferOffset, bufferOrigin, bufferLength );
+ auto_ptr<PacketMachine> ap ( pm );
+ snipPos->fMachine = ap;
+ }
+ #endif
+ thisMachine = snipPos->fMachine.get();
+ }
+
+ bool bufferDone = false;
+ while ( ! bufferDone ) {
+
+ PacketMachine::TriState foundPacket = thisMachine->FindNextPacket();
+
+ if ( foundPacket == PacketMachine::eTriNo ) {
+
+ // -----------------------------------------------------------------------
+ // No packet, mark the snip as raw data and get rid of the packet machine.
+ // We're done with this buffer.
+
+ snipPos->fInfo.fState = eRawInputSnip;
+ #if 0
+ snipPos->fMachine = auto_ptr<PacketMachine>(); // *** snipPos->fMachine.reset(); VC++ lacks reset
+ #else
+ {
+ // Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
+ auto_ptr<PacketMachine> ap ( 0 );
+ snipPos->fMachine = ap;
+ }
+ #endif
+ bufferDone = true;
+
+ } else {
+
+ // ---------------------------------------------------------------------------------------------
+ // Either a full or partial packet. First trim any excess off of the front as a raw input snip.
+ // If this is a partial packet mark the snip and keep the packet machine to be resumed later.
+ // We're done with this buffer, the partial packet by definition extends to the end. If this is
+ // a complete packet first extract the additional information from the packet machine. If there
+ // is leftover data split the snip and transfer the packet machine to the new trailing snip.
+
+ if ( thisMachine->fPacketStart > snipPos->fInfo.fOffset ) {
+
+ // There is data at the front of the current snip that must be trimmed.
+ SnipState savedState = snipPos->fInfo.fState;
+ snipPos->fInfo.fState = eRawInputSnip; // ! So it gets propagated to the trimmed front part.
+ relOffset = thisMachine->fPacketStart - snipPos->fInfo.fOffset;
+ SplitInternalSnip ( snipPos, relOffset, (snipPos->fInfo.fLength - relOffset) );
+ snipPos->fInfo.fState = savedState;
+
+ }
+
+ if ( foundPacket == PacketMachine::eTriMaybe ) {
+
+ // We have only found a partial packet.
+ snipPos->fInfo.fState = ePartialPacketSnip;
+ bufferDone = true;
+
+ } else {
+
+ // We have found a complete packet. Extract all the info for it and split any trailing data.
+
+ InternalSnipIterator packetSnip = snipPos;
+ SnipState packetState = eValidPacketSnip;
+
+ if ( thisMachine->fBogusPacket ) packetState = eBadPacketSnip;
+
+ packetSnip->fInfo.fAccess = thisMachine->fAccess;
+ packetSnip->fInfo.fCharForm = thisMachine->fCharForm;
+ packetSnip->fInfo.fBytesAttr = thisMachine->fBytesAttr;
+ packetSnip->fInfo.fEncodingAttr = thisMachine->fEncodingAttr.c_str();
+ thisMachine->fEncodingAttr.erase ( thisMachine->fEncodingAttr.begin(), thisMachine->fEncodingAttr.end() );
+
+ if ( (thisMachine->fCharForm != eChar8Bit) && CharFormIsBigEndian ( thisMachine->fCharForm ) ) {
+
+ // ------------------------------------------------------------------------------
+ // Handle a special case for big endian characters. The packet machine works as
+ // though things were little endian. The packet starting offset points to the
+ // byte containing the opening '<', and the length includes presumed nulls that
+ // follow the last "real" byte. If the characters are big endian we now have to
+ // decrement the starting offset of the packet, and also decrement the length of
+ // the previous snip.
+ //
+ // Note that we can't do this before the head trimming above in general. The
+ // nulls might have been exactly at the end of a buffer and already in the
+ // previous snip. We are doing this before trimming the tail from the raw snip
+ // containing the packet. We adjust the raw snip's size because it ends with
+ // the input buffer. We don't adjust the packet's size, it is already correct.
+ //
+ // The raw snip (the one before the packet) might entirely disappear. A simple
+ // example of this is when the packet is at the start of the file.
+
+ assert ( packetSnip != fInternalSnips.begin() ); // Leading nulls were trimmed!
+
+ if ( packetSnip != fInternalSnips.begin() ) { // ... but let's program defensibly.
+
+ InternalSnipIterator prevSnip = PrevSnip ( packetSnip );
+ const unsigned int nullsToAdd = ( CharFormIs16Bit ( thisMachine->fCharForm ) ? 1 : 3 );
+
+ assert ( nullsToAdd <= prevSnip->fInfo.fLength );
+ prevSnip->fInfo.fLength -= nullsToAdd;
+ if ( prevSnip->fInfo.fLength == 0 ) (void) fInternalSnips.erase ( prevSnip );
+
+ packetSnip->fInfo.fOffset -= nullsToAdd;
+ packetSnip->fInfo.fLength += nullsToAdd;
+ thisMachine->fPacketStart -= nullsToAdd;
+
+ }
+
+ }
+
+ if ( thisMachine->fPacketLength == snipPos->fInfo.fLength ) {
+
+ // This packet ends exactly at the end of the current snip.
+ #if 0
+ snipPos->fMachine = auto_ptr<PacketMachine>(); // *** snipPos->fMachine.reset(); VC++ lacks reset
+ #else
+ {
+ // Some versions of gcc complain about the assignment operator above. This avoids the gcc bug.
+ auto_ptr<PacketMachine> ap ( 0 );
+ snipPos->fMachine = ap;
+ }
+ #endif
+ bufferDone = true;
+
+ } else {
+
+ // There is trailing data to split from the just found packet.
+ SplitInternalSnip ( snipPos, 0, thisMachine->fPacketLength );
+
+ InternalSnipIterator tailPos = NextSnip ( snipPos );
+
+ tailPos->fMachine = snipPos->fMachine; // auto_ptr assignment - taking ownership
+ thisMachine->ResetMachine ();
+
+ snipPos = tailPos;
+
+ }
+
+ packetSnip->fInfo.fState = packetState; // Do this last to avoid messing up the tail split.
+ // DumpSnipList ( "Found a packet" );
+
+ }
+
+ }
+
+ }
+
+ // --------------------------------------------------------
+ // Merge this snip with the preceeding snip if appropriate.
+
+ // *** When out of order I/O is supported we have to check the following snip too.
+
+ if ( (snipPos->fInfo.fOffset > 0) && (snipPos->fInfo.fState == eRawInputSnip) ) {
+ InternalSnipIterator prevPos = PrevSnip ( snipPos );
+ if ( prevPos->fInfo.fState == eRawInputSnip ) snipPos = MergeInternalSnips ( prevPos, snipPos );
+ }
+
+ // DumpSnipList ( "After scan" );
+
+} // Scan
+
+// =================================================================================================
+// Report
+// ======
+
+void
+XMPScanner::Report ( SnipInfoVector& snips )
+{
+ const int count = (int)fInternalSnips.size();
+ InternalSnipIterator snipPos = fInternalSnips.begin();
+
+ int s;
+
+ // DumpSnipList ( "Report" );
+
+ snips.erase ( snips.begin(), snips.end() ); // ! Should use snips.clear, but VC++ doesn't have it.
+ snips.reserve ( count );
+
+ for ( s = 0; s < count; s += 1 ) {
+ snips.push_back ( SnipInfo ( snipPos->fInfo.fState, snipPos->fInfo.fOffset, snipPos->fInfo.fLength ) );
+ snips[s] = snipPos->fInfo; // Pick up all of the fields.
+ ++ snipPos;
+ }
+
+} // Report
+
+// =================================================================================================
diff --git a/samples/source/common/XMPScanner.hpp b/samples/source/common/XMPScanner.hpp
new file mode 100644
index 0000000..1479d2d
--- /dev/null
+++ b/samples/source/common/XMPScanner.hpp
@@ -0,0 +1,329 @@
+#ifndef __XMPScanner_hpp__
+#define __XMPScanner_hpp__
+
+// =================================================================================================
+// Copyright 2004 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// Adobe patent application tracking #P435, entitled 'Unique markers to simplify embedding data of
+// one format in a file with a different format', inventors: Sean Parent, Greg Gilley.
+// =================================================================================================
+
+#include "public/include/XMP_Environment.h" // ! This must be the first include.
+#include "public/include/XMP_Const.h"
+
+#include <list>
+#include <vector>
+#include <string>
+#include <memory>
+#include <stdexcept>
+
+// =================================================================================================
+// The XMPScanner class is used to scan a stream of input for XMP packets. A scanner object is
+// constructed then fed the input through a series of calls to Scan. Report may be called at any
+// time to get the current knowledge of the input.
+//
+// A packet starts when a valid header is found and ends when a valid trailer is found. If the
+// header contains a "bytes" attribute, additional whitespace must follow.
+//
+// *** RESTRICTIONS: The current implementation of the scanner has the the following restrictions:
+// - The input must be presented in order.
+// - Not fully thread safe, don't make concurrent calls to the same XMPScanner object.
+// =================================================================================================
+
+class XMPScanner {
+public:
+
+ // =============================================================================================
+ // The entire input stream is represented as a series of snips. Each snip defines one portion
+ // of the input stream that either has not been seen, has been seen and contains no packets, is
+ // exactly one packet, or contains the start of an unfinished packet. Adjacent snips with the
+ // same state are merged, so the number of snips is always minimal.
+ //
+ // A newly constructed XMPScanner object has one snip covering the whole input with a state
+ // of "not seen". A block of input that contains a full XMP packet is split into 3 parts: a
+ // (possibly empty) raw input snip, the packet, and another (possibly empty) raw input snip. A
+ // block of input that contains the start of an XMP packet is split into two snips, a (possibly
+ // empty) raw input snip and the packet start; the following snip must be a "not seen" snip.
+ //
+ // It is possible to have ill-formed packets. These have a syntactically valid header and
+ // trailer, but some semantic error. For example, if the "bytes" attribute length does not span
+ // to the end of the trailer, or if the following packet begins within trailing padding.
+
+ enum {
+ eNotSeenSnip, // This snip has not been seen yet.
+ ePendingSnip, // This snip is an input buffer being processed.
+ eRawInputSnip, // This snip is raw input, it doesn't contain any part of an XMP packet.
+ eValidPacketSnip, // This snip is a complete, valid XMP packet.
+ ePartialPacketSnip, // This snip contains the start of a possible XMP packet.
+ eBadPacketSnip // This snip contains a complete, but semantically incorrect XMP packet.
+ };
+ typedef XMP_Uns8 SnipState;
+
+ enum { // The values allow easy testing for 16/32 bit and big/little endian.
+ eChar8Bit = 0,
+ eChar16BitBig = 2,
+ eChar16BitLittle = 3,
+ eChar32BitBig = 4,
+ eChar32BitLittle = 5
+ };
+ typedef XMP_Uns8 CharacterForm;
+
+ enum {
+ eChar16BitMask = 2, // These constant shouldn't be used directly, they are mainly
+ eChar32BitMask = 4, // for the CharFormIsXyz macros below.
+ eCharLittleEndianMask = 1
+ };
+
+ #define CharFormIs16Bit(f) ( ((int)(f) & XMPScanner::eChar16BitMask) != 0 )
+ #define CharFormIs32Bit(f) ( ((int)(f) & XMPScanner::eChar32BitMask) != 0 )
+
+ #define CharFormIsBigEndian(f) ( ((int)(f) & XMPScanner::eCharLittleEndianMask) == 0 )
+ #define CharFormIsLittleEndian(f) ( ((int)(f) & XMPScanner::eCharLittleEndianMask) != 0 )
+
+ struct SnipInfo {
+
+ XMP_Int64 fOffset; // The byte offset of this snip within the input stream.
+ XMP_Int64 fLength; // The length in bytes of this snip.
+ SnipState fState; // The state of this snip.
+ bool fOutOfOrder; // If true, this snip was seen before the one in front of it.
+ char fAccess; // The read-only/read-write access from the end attribute.
+ CharacterForm fCharForm; // How the packet is divided into characters.
+ const char * fEncodingAttr; // The value of the encoding attribute, if any, with nulls removed.
+ XMP_Int64 fBytesAttr; // The value of the bytes attribute, -1 if not present.
+
+ SnipInfo() :
+ fOffset ( 0 ),
+ fLength ( 0 ),
+ fState ( eNotSeenSnip ),
+ fOutOfOrder ( false ),
+ fAccess ( ' ' ),
+ fCharForm ( eChar8Bit ),
+ fEncodingAttr ( "" ),
+ fBytesAttr( -1 )
+ { }
+
+ SnipInfo ( SnipState state, XMP_Int64 offset, XMP_Int64 length ) :
+ fOffset ( offset ),
+ fLength ( length ),
+ fState ( state ),
+ fOutOfOrder ( false ),
+ fAccess ( ' ' ),
+ fCharForm ( eChar8Bit ),
+ fEncodingAttr ( "" ),
+ fBytesAttr( -1 )
+ { }
+
+ };
+
+ typedef std::vector<SnipInfo> SnipInfoVector;
+
+ XMPScanner ( XMP_Int64 streamLength );
+ // Constructs a new XMPScanner object for a stream with the given length.
+
+ ~XMPScanner();
+
+ long GetSnipCount();
+ // Returns the number of snips that the stream has been divided into.
+
+ bool StreamAllScanned();
+ // Returns true if all of the stream has been seen.
+
+ void Scan ( const void * bufferOrigin, XMP_Int64 bufferOffset, XMP_Int64 bufferLength );
+ // Scans the given part of the input, incorporating it in to the known snips.
+ // The bufferOffset is the offset of this block of input relative to the entire stream.
+ // The bufferLength is the length in bytes of this block of input.
+
+ void Report ( SnipInfoVector & snips );
+ // Produces a report of what is known about the input stream.
+
+ class ScanError : public std::logic_error {
+ public:
+ ScanError() throw() : std::logic_error ( "" ) {}
+ explicit ScanError ( const char * message ) throw() : std::logic_error ( message ) {}
+ virtual ~ScanError() throw() {}
+ };
+
+private: // XMPScanner
+
+ class PacketMachine;
+
+ class InternalSnip {
+ public:
+
+ SnipInfo fInfo; // The public info about this snip.
+ std::auto_ptr<PacketMachine> fMachine; // The state machine for "active" snips.
+
+ InternalSnip ( XMP_Int64 offset, XMP_Int64 length );
+ InternalSnip ( const InternalSnip & );
+ ~InternalSnip ();
+
+ }; // InternalSnip
+
+ typedef std::list<InternalSnip> InternalSnipList;
+ typedef InternalSnipList::iterator InternalSnipIterator;
+
+ class PacketMachine {
+ public:
+
+ XMP_Int64 fPacketStart; // Byte offset relative to the entire stream.
+ XMP_Int32 fPacketLength; // Length in bytes to the end of the trailer processing instruction.
+ XMP_Int32 fBytesAttr; // The value of the bytes attribute, -1 if not present.
+ std::string fEncodingAttr; // The value of the encoding attribute, if any, with nulls removed.
+ CharacterForm fCharForm; // How the packet is divided into characters.
+ char fAccess; // The read-only/read-write access from the end attribute.
+ bool fBogusPacket; // True if the packet has an error such as a bad "bytes" attribute value.
+
+ void ResetMachine();
+
+ enum TriState {
+ eTriNo,
+ eTriMaybe,
+ eTriYes
+ };
+
+ TriState FindNextPacket();
+
+ void AssociateBuffer ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength );
+
+ PacketMachine ( XMP_Int64 bufferOffset, const void * bufferOrigin, XMP_Int64 bufferLength );
+ ~PacketMachine();
+
+ private: // PacketMachine
+
+ PacketMachine() {}; // ! Hide the default constructor.
+
+ enum RecognizerKind {
+
+ eFailureRecognizer, // Not really recognizers, special states to end one buffer's processing.
+ eSuccessRecognizer,
+
+ eLeadInRecognizer, // Anything up to the next '<'.
+ eHeadStartRecorder, // Save the starting offset, count intervening nulls.
+ eHeadStartRecognizer, // The literal string "?xpacket begin=".
+
+ eBOMRecognizer, // Recognize and record the quoted byte order marker.
+
+ eIDTagRecognizer, // The literal string " id=".
+ eIDOpenRecognizer, // The opening quote for the ID.
+ eIDValueRecognizer, // The literal string "W5M0MpCehiHzreSzNTczkc9d".
+ eIDCloseRecognizer, // The closing quote for the ID.
+
+ eAttrSpaceRecognizer_1, // The space before an attribute.
+ eAttrNameRecognizer_1, // The name of an attribute.
+ eAttrValueRecognizer_1, // The equal sign and quoted string value for an attribute.
+ eAttrValueRecorder_1, // Record the value of an attribute.
+
+ eHeadEndRecognizer, // The string literal "?>".
+
+ eBodyRecognizer, // The packet body, anything up to the next '<'.
+
+ eTailStartRecognizer, // The string literal "?xpacket end=".
+ eAccessValueRecognizer, // Recognize and record the quoted r/w access mode.
+
+ eAttrSpaceRecognizer_2, // The space before an attribute.
+ eAttrNameRecognizer_2, // The name of an attribute.
+ eAttrValueRecognizer_2, // The equal sign and quoted string value for an attribute.
+ eAttrValueRecorder_2, // Record the value of an attribute.
+
+ eTailEndRecognizer, // The string literal "?>".
+ ePacketEndRecognizer, // Look for trailing padding, check and record the packet size.
+ eCloseOutRecognizer, // Look for final nulls for little endian multibyte characters.
+
+ eRecognizerCount
+
+ };
+
+ XMP_Int64 fBufferOffset; // The offset of the data buffer within the input stream.
+ const char * fBufferOrigin; // The starting address of the data buffer for this snip.
+ const char * fBufferPtr; // The current postion in the data buffer.
+ const char * fBufferLimit; // The address one past the last byte in the data buffer.
+
+ RecognizerKind fRecognizer; // Which recognizer is currently active.
+ signed long fPosition; // The internal position within a string literal, etc.
+ unsigned char fBytesPerChar; // The number of bytes per logical character, 1, 2, or 4.
+ unsigned char fBufferOverrun; // Non-zero if suspended while skipping intervening nulls.
+ char fQuoteChar; // The kind of quote seen at the start of a quoted value.
+ std::string fAttrName; // The name for an arbitrary attribute (other than "begin" and "id").
+ std::string fAttrValue; // The value for an arbitrary attribute (other than "begin" and "id").
+
+ void SetNextRecognizer ( RecognizerKind nextRecognizer );
+
+ typedef TriState (* RecognizerProc) ( PacketMachine *, const char * );
+
+ static TriState
+ FindLessThan ( PacketMachine * ths, const char * which );
+
+ static TriState
+ MatchString ( PacketMachine * ths, const char * literal );
+
+ static TriState
+ MatchChar ( PacketMachine * ths, const char * literal );
+
+ static TriState
+ MatchOpenQuote ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ MatchCloseQuote ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CaptureAttrName ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CaptureAttrValue ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecordStart ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecognizeBOM ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecordHeadAttr ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CaptureAccess ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ RecordTailAttr ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CheckPacketEnd ( PacketMachine * ths, const char * /* unused */ );
+
+ static TriState
+ CheckFinalNulls ( PacketMachine * ths, const char * /* unused */ );
+
+ struct RecognizerInfo {
+ RecognizerProc proc;
+ RecognizerKind successNext;
+ RecognizerKind failureNext;
+ const char * literal;
+ };
+
+ }; // PacketMachine
+
+ XMP_Int64 fStreamLength;
+ InternalSnipList fInternalSnips;
+
+ void
+ SplitInternalSnip ( InternalSnipIterator snipPos, XMP_Int64 relOffset, XMP_Int64 newLength );
+
+ InternalSnipIterator
+ MergeInternalSnips ( InternalSnipIterator firstPos, InternalSnipIterator secondPos );
+
+ InternalSnipIterator
+ PrevSnip ( InternalSnipIterator snipPos );
+
+ InternalSnipIterator
+ NextSnip ( InternalSnipIterator snipPos );
+
+ #if DEBUG
+ void DumpSnipList ( const char * title );
+ #endif
+
+}; // XMPScanner
+
+#endif // __XMPScanner_hpp__
diff --git a/samples/source/common/globals.h b/samples/source/common/globals.h
new file mode 100644
index 0000000..0de8124
--- /dev/null
+++ b/samples/source/common/globals.h
@@ -0,0 +1,99 @@
+// =================================================================================================
+// Copyright 2007 Adobe Systems Incorporated
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it.
+//
+// =================================================================================================
+
+#ifndef XMPQE_GLOBALS_H
+#define XMPQE_GLOBALS_H
+
+ #include <string>
+
+ #include <cstdlib> // Various libraries needed for gcc 4.x builds.
+ #include <cstring>
+ #include <cstdio>
+
+ //sanity check platform/endianess
+ #if !defined(WIN_ENV) && !defined(MAC_ENV) && !defined(UNIX_ENV) && !defined(IOS_ENV) && !defined(ANDROID_ENV)
+ #error "XMP environment error - must define one of MAC_ENV, WIN_ENV, UNIX_ENV or IOS_ENV or ANDROID_ENV"
+ #endif
+
+ #ifdef WIN_ENV
+ #define XMPQE_LITTLE_ENDIAN 1
+ #elif (defined(MAC_ENV) || defined(UNIX_ENV) || defined(IOS_ENV)) || defined(ANDROID_ENV)
+ #if __BIG_ENDIAN__
+ #define XMPQE_BIG_ENDIAN 1
+ #elif __LITTLE_ENDIAN__
+ #define XMPQE_LITTLE_ENDIAN 1
+ #else
+ #error "Neither __BIG_ENDIAN__ nor __LITTLE_ENDIAN__ is set"
+ #endif
+ #else
+ #error "Unknown build environment, neither WIN_ENV nor MAC_ENV nor UNIX_ENV nor ANDROID_ENV"
+ #endif
+
+ const static unsigned int XMPQE_BUFFERSIZE=4096; //should do for all my buffer output, but you never now (stdarg dilemma)
+
+
+
+ const char OMNI_CSTRING[]={0x41,0xE4,0xB8,0x80,0x42,0xE4,0xBA,0x8C,0x43,0xC3,0x96,0x44,0xF0,0x90,0x81,0x91,0x45,'\0'};
+ const char BOM_CSTRING[]={0xEF,0xBB,0xBF,'\0'}; // nb: forgetting the '\0' is a very evil mistake.
+ const std::string OMNI_STRING(OMNI_CSTRING);
+ // if plain utf8 conversion, mac/win local encoding is a different story...
+ const std::string OMNI_BUGINESE("A<E4 B8 80>B<E4 BA 8C>C<C3 96>D<F0 90 81 91>E");
+ // if utf16LE
+ const std::string OMNI_BUGINESE_16LE("A<00 4E>B<8C 4E>C<D6 00>D<00 D8 51 DC>E");
+ // if utf16 BE
+ const std::string OMNI_BUGINESE_16BE("A<4E 00>B<4E 8C>C<00 D6>D<D8 00 DC 51>E");
+
+ // degraded version of omni-strings
+ // (sometimes useful for asserts after roundtrips, for setting values see OMNI_STRING in TestCase.h)
+ // ==> filed bug 2302354 on this issue.
+ #if WIN_ENV
+ // a *wrongly* degraded omni-string (non-BMP-char to ??)
+ const std::string DEG_OMNI_BUGINESE("A?B?C<C3 96>D??E");
+ // ditto albeit MacRoman encoding
+ const std::string MAC_OMNI_BUGINESE("A?B?C<85>D??E");
+ #else
+ // a *correctly* degraded omni-string (non-BMP-char to ?)
+ const std::string DEG_OMNI_BUGINESE("A?B?C<C3 96>D?E");
+ // ditto albeit MacRoman encoding
+ const std::string MAC_OMNI_BUGINESE("A?B?C<85>D?E");
+ #endif
+ // -> #issue# the non-BMP character in OMNI_STRING between D and E gets converted
+ // into two question marks (wrong) , not into one (correct)
+
+ const char AEOEUE_WIN_LOCAL_CSTRING[]={0xC4,0xD6,0xDC,'\0'};
+ const char AEOEUE_MAC_LOCAL_CSTRING[]={0x80,0x85,0x86,'\0'};
+ const char AEOEUE_UTF8_CSTRING[]={0xC3, 0x84, 0xC3, 0x96, 0xC3, 0x9C,'\0'};
+ const std::string AEOEUE_UTF8(AEOEUE_UTF8_CSTRING);
+ const std::string AEOEUE_UTF8_BUGINESE("<C3 84 C3 96 C3 9C>");
+ const std::string AEOEUE_UTF8_BUGINESE_EVEN ( "<C3 84 C3 96 C3 9C 00>" );
+
+ const std::string AEOEUE_WIN_LOCAL_BUGINESE("<C4 D6 DC>");
+ const std::string AEOEUE_WIN_LOCAL_BUGINESE_EVEN ( "<C4 D6 DC 00>" );
+ const std::string AEOEUE_MAC_LOCAL_BUGINESE("<80 85 86>");
+
+ const std::string AEOEUE_WIN_MOJIBAKE_BUGINESE("<E2 82 AC E2 80 A6 E2 80 A0>");
+ const std::string AEOEUE_MAC_MOJIBAKE_BUGINESE("<C6 92 C3 B7 E2 80 B9>");
+ const std::string AEOEUE_LATIN1_MOJIBAKE_BUGINESE("<C2 80 C2 85 C2 86>");
+
+ #if MAC_ENV || IOS_ENV
+ const std::string AEOEUE_LOCAL = std::string(AEOEUE_MAC_LOCAL_CSTRING);
+ const std::string AEOEUE_WIN_LOCAL_TO_UTF8 = AEOEUE_MAC_MOJIBAKE_BUGINESE;
+ const std::string AEOEUE_MAC_LOCAL_TO_UTF8 = AEOEUE_UTF8_BUGINESE;
+ #elif WIN_ENV
+ const std::string AEOEUE_LOCAL = std::string(AEOEUE_WIN_LOCAL_CSTRING);
+ const std::string AEOEUE_WIN_LOCAL_TO_UTF8 = AEOEUE_UTF8_BUGINESE;
+ const std::string AEOEUE_MAC_LOCAL_TO_UTF8 = AEOEUE_WIN_MOJIBAKE_BUGINESE;
+ #else
+ // windows local encoding will work for UNIX (Latin1), but mac will result MOJIBAKE
+ const std::string AEOEUE_WIN_LOCAL_TO_UTF8 = AEOEUE_UTF8_BUGINESE;
+ const std::string AEOEUE_MAC_LOCAL_TO_UTF8 = AEOEUE_LATIN1_MOJIBAKE_BUGINESE;
+ #endif
+
+#endif // XMPQE_GLOBALS_H
+
diff --git a/samples/source/dumpfile/main.cpp b/samples/source/dumpfile/main.cpp
new file mode 100644
index 0000000..847f2d7
--- /dev/null
+++ b/samples/source/dumpfile/main.cpp
@@ -0,0 +1,241 @@
+// =================================================================================================
+// Copyright 2003 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+//
+//
+// Dumpfile is a dev utility to understand, and to a certain degree validate file structures, with
+// specific focus on metadata related aspects, naturally. Dumpfile is NOT meant to extract
+// valid metadata from files. Dumpfile is NOT meant to be a production tool.
+// NO reconciliation, NO XMP logic, NO attention to encoding matters, etc.
+//
+// Dumpfile gives developers a view on files just like a hex editor,
+// slightly more readible but in no way more sophisticated.
+
+//sanity check platform/endianess
+#include "samples/source/common/globals.h"
+#if XMP_WinBuild
+ #pragma warning ( disable : 4267 ) // suppress string conversion warning
+ // #pragma warning ( disable : 1234 ) // say what you do here
+#endif
+
+//only define in one non-public-source, non-header(.cpp) place
+
+const int DUMPFILEVERSION=2;
+#define EXENAME "dumpfile"
+
+//standard stuff
+#include <stdexcept>
+#include <iostream>
+#include <string>
+#include <cstring>
+#include <cstdio>
+#include <vector>
+#include <sstream>
+
+//XMPCore (only!)
+#define TXMP_STRING_TYPE std::string
+#include "public/include/XMP.hpp" //NB: no XMP.incl_cpp here on purpose, gets compiled in main...
+#include "public/include/XMP.incl_cpp" //include in EXACTLY one source file (i.e. main, in Action gets you trouble...)
+#include "public/include/XMP_Const.h"
+#include "source/XML_Node.cpp"
+
+//utils
+#include "samples/source/common/Log.h"
+#include "samples/source/common/LargeFileAccess.hpp"
+#include "samples/source/common/DumpFile.h"
+using namespace std;
+
+void printUsageShort() {
+ Log::info("%s -version",EXENAME);
+ Log::info(" Print version information for this utility");
+ Log::info("");
+ Log::info("%s -help",EXENAME);
+ Log::info(" Print descriptions of all available switches, including examples");
+ Log::info("");
+ Log::info("%s [ -help | -version | [-keys|-tree|-list] [-nocomments] <path> ]",EXENAME);
+ Log::info("");
+ Log::info("Copyright 2008 Adobe Systems Incorporated. All Rights Reserved.");
+ Log::info("");
+}
+
+void printUsageLong() {
+ Log::info("%s -version",EXENAME);
+ Log::info(" Print version information for this utility");
+ Log::info("");
+ Log::info("%s -help",EXENAME);
+ Log::info(" Print descriptions of all available switches, including examples");
+ Log::info("");
+ Log::info("%s [ -help | -version | [-keys|-tree|-list] [-nocomments] <path> ]",EXENAME);
+ Log::info("");
+ Log::info("Copyright 2008 Adobe Systems Incorporated. All Rights Reserved.");
+ Log::info("NOTICE: Adobe permits you to use, modify, and distribute this file in");
+ Log::info(" accordance with the terms of the Adobe license agreement");
+ Log::info(" accompanying it.");
+ Log::info("");
+ Log::info("Switches:");
+ Log::info("-help Prints usage information for the command.");
+ Log::info("");
+ Log::info("-version Prints version information for this tool, and for the version");
+ Log::info(" of XMPCore to which it is statically linked. (NB: Dumpfile does");
+ Log::info(" not use XMPFiles.");
+ Log::info("");
+ Log::info("-tree Shows the tree structure of the file. (default)");
+ Log::info("");
+ Log::info("-keys Shows only the key-value nodes found in the file, as a list with no");
+ Log::info(" hierarchical structure, in alphabetical order by key.");
+ Log::info("");
+ Log::info("-list Shows only the key-value nodes found in the file, as a list with no");
+ Log::info(" hierarchical structure, in the order of parsing.");
+ Log::info("");
+ Log::info("-nocomments Supresses the comment portion of key-value nodes.");
+ Log::info("");
+
+ Log::info("");
+ Log::info("Examples:");
+ Log::info("%s -keys Sample.jpg",EXENAME);
+ Log::info("");
+ Log::info("dumpfile returns 0 if there are no errors. Note: Warnings and errors");
+ Log::info("are printed as part of the stdout output, not to stderr.");
+ Log::info("");
+}
+
+int
+main( int argc, char* argv[] )
+{
+ //if both not flagged will default to tree-only (below)
+ bool treeFlag=false;
+ bool keysFlag=false;
+ bool listFlag=false;
+ bool commentsFlag=true; // true <=> do not supress comments (supressing them is better for kgo)
+
+ //NB: not logging to file since mostly pointless...
+ int returnValue=0; //assume all will be good
+
+ int i=1; //define outside scope to access in catch clause
+ try {
+ if ( ! SXMPMeta::Initialize( ) )
+ {
+ Log::info( "SXMPMeta::Initialize failed!" );
+ return -1;
+ }
+
+ //no parameters? print usage.
+ if ( argc == 1 )
+ {
+ printUsageShort();
+ return 0;
+ }
+
+ //one parameter? check...
+ if (
+ !strcmp("-help",argv[1]) ||
+ !strcmp("--help",argv[1]) ||
+ !strcmp("-h",argv[1]) ||
+ !strcmp("-?",argv[1]) ||
+ !strcmp("/?",argv[1]) )
+ {
+ printUsageLong();
+ return 0;
+ }
+ else if ( !strcmp("-version",argv[1]) )
+ {
+ XMP_VersionInfo coreVersion;
+ SXMPMeta::GetVersionInfo ( &coreVersion );
+ Log::info("using XMPCore %s", coreVersion.message );
+ Log::info("dumpfile Version %d", DUMPFILEVERSION );
+ return 0;
+ }
+
+ //multiple parameters? all but last must be switches!
+ while (i < argc-1) //while not last parameter
+ {
+ if ( !strcmp("-keys",argv[i]) )
+ keysFlag=true;
+ else if ( !strcmp("-tree",argv[i]) )
+ treeFlag=true;
+ else if ( !strcmp("-list",argv[i]) )
+ listFlag=true;
+ else if ( !strcmp("-nocomments",argv[i]) )
+ commentsFlag=false;
+ else if (argv[i][0] == '-') //yet-another-switch?
+ {
+ Log::info("unknown switch %s",argv[i]);
+ printUsageShort();
+ return -1;
+ }
+ else
+ {
+ Log::info("only one image at a time: %s",argv[i]);
+ printUsageShort();
+ return -1;
+ }
+ i++;
+ } //while
+
+ if (!treeFlag && !keysFlag && !listFlag) //if nothing request default to "-tree"
+ treeFlag=true;
+
+ TagTree tree;
+ std::string errMsg;
+ try //parsing failures on incorrect files should still lead to partial dump
+ {
+ DumpFile::Scan(argv[i],tree);
+ }
+ catch (DumpFileException& d)
+ {
+ tree.popAllNodes();
+ returnValue = -9; //earmark but keep going
+ errMsg=d.what_dumpfile_reason();
+ }
+
+ if (treeFlag)
+ tree.dumpTree(commentsFlag);
+ if (treeFlag && keysFlag) //separator if needed
+ Log::info("------------------------------------------------------------------------");
+ if (keysFlag)
+ tree.dumpTagMap();
+ if ( (treeFlag || keysFlag) && listFlag) //separator if needed
+ Log::info("------------------------------------------------------------------------");
+ if (listFlag)
+ tree.dumpTagList();
+
+ //order appears more logical to print after the dumping happended:
+ if (returnValue != 0) {
+ Log::info("parsing failed. \n%s",errMsg.c_str());
+ }
+
+ SXMPMeta::Terminate();
+ }
+ catch (XMP_Error& x ) {
+ Log::info("XMPError on file %s:\n%s", argv[i] , x.GetErrMsg() );
+ return -3;
+ }
+ catch (LOG_Exception& q) {
+ //also used by assert which are very self-explanatory
+ //neutral wording for standalone app
+ Log::info("INTERNAL parsing error\n- file: %s\n%s", argv[i] , q.what() );
+ return -4;
+ }
+ catch ( std::exception &e )
+ {
+ Log::info("std:exception on file %s:\n%s", argv[i] , e.what() );
+ return -5;
+ }
+ catch (...)
+ {
+ Log::info("unknown (...) exception on file %s", argv[i] );
+ return -6;
+ }
+
+ if (returnValue==0)
+ Log::info("OK");
+ else
+ Log::info("ERROR(s) encountered");
+ return returnValue;
+}
diff --git a/samples/source/xmpcommand/Actions.cpp b/samples/source/xmpcommand/Actions.cpp
new file mode 100644
index 0000000..c8848f5
--- /dev/null
+++ b/samples/source/xmpcommand/Actions.cpp
@@ -0,0 +1,441 @@
+// =================================================================================================
+// Copyright 2006 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+// actions.cpp|h
+// performs one of the XMPFiles actions as indicated by the paramters (and evaluated in main)
+//
+
+const char * XMP_EXE_VERSION= "4.4";
+
+#include <stdexcept>
+#include <cstdarg>
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+
+//XMP related
+#define TXMP_STRING_TYPE std::string
+#define XMP_INCLUDE_XMPFILES 1
+#include "public/include/XMP.hpp" //NB: no XMP.incl_cpp here on purpose, gets compiled in main...
+#include "public/include/XMP_Const.h"
+
+#include "samples/source/common/globals.h"
+#include "samples/source/common/Log.h"
+#include "samples/source/xmpcommand/Actions.h" //must come after XMP.hpp/XMP_Const.h
+
+// utility functions **************************************************************
+
+ /* does NOT include path fixing (unix vs wxp) on purpose
+ * being readable of course implies existing
+ * - note: this utility is also used by cppunit sanity checks
+ */
+ bool fileIsReadable(const std::string filename)
+ {
+ FILE* fp = NULL;
+ fp = fopen( filename.c_str(), "rb" );
+ if( fp != NULL )
+ {
+ fclose( fp );
+ return true;
+ }
+ return false;
+ }
+
+ bool fileIsWritable(const std::string filename)
+ {
+ FILE* fp = NULL;
+ fp = fopen( filename.c_str(), "rb+" );
+ if( fp != NULL )
+ {
+ fclose( fp );
+ return true;
+ }
+ return false;
+ }
+
+ void verifyFileIsReadable(const std::string filename)
+ {
+ if (! fileIsReadable(filename))
+ Log::error("file %s is not readable or not existing.",filename.c_str());
+
+ }
+
+ void verifyFileIsWritable(const std::string filename)
+ {
+ if (! fileIsWritable(filename))
+ Log::error("file %s is not readwritable or not existing.",filename.c_str());
+ }
+
+
+ std::string getStringFromFile(const std::string& filename)
+ {
+ verifyFileIsReadable(filename);
+
+ //figure out length
+ FILE * file = fopen ( filename.c_str(), "rb" );
+ fseek ( file, 0, SEEK_END );
+ XMP_Uns32 length = ftell( file );
+ fseek ( file, 0, SEEK_SET );
+
+ //write into string
+ std::string content;
+ content.reserve ( (XMP_Uns32) length );
+ content.append ( length, ' ' );
+ fread ( (char*)content.data(), 1, length, file );
+ fclose ( file );
+ return content;
+ }
+
+ /* hand over the fileFormat and get a fileFormatName (2-3 Letters ie. PDF)
+ * and a Description (i.e. "Portable Document Format") back.
+ * see also public/include/XMP_Const.h
+ */
+ void fileFormatNameToString ( XMP_FileFormat fileFormat, std::string & fileFormatName, std::string & fileFormatDesc )
+ {
+ fileFormatName.erase();
+ fileFormatDesc.erase();
+ switch(fileFormat) {
+ case kXMP_PDFFile:
+ fileFormatName = "PDF ";fileFormatDesc = "Portable Document Format";break;
+ case kXMP_PostScriptFile:
+ fileFormatName = "PS ";fileFormatDesc = "Post Script";break;
+ case kXMP_EPSFile:
+ fileFormatName = "EPS ";fileFormatDesc = "Encapsulated Post Script";break;
+ case kXMP_JPEGFile:
+ fileFormatName = "JPEG";fileFormatDesc = "Joint Photographic Experts Group";break;
+ case kXMP_JPEG2KFile:
+ fileFormatName = "JPX ";fileFormatDesc = "JPEG 2000";break;
+ case kXMP_TIFFFile:
+ fileFormatName = "TIFF";fileFormatDesc = "Tagged Image File Format";break;
+ case kXMP_GIFFile:
+ fileFormatName = "GIF ";fileFormatDesc = "Graphics Interchange Format";break;
+ case kXMP_PNGFile:
+ fileFormatName = "PNG ";fileFormatDesc = "Portable Network Graphic";break;
+ case kXMP_MOVFile:
+ fileFormatName = "MOV ";fileFormatDesc = "Quicktime";break;
+ case kXMP_AVIFile:
+ fileFormatName = "AVI ";fileFormatDesc = "Quicktime";break;
+ case kXMP_CINFile:
+ fileFormatName = "CIN ";fileFormatDesc = "Cineon";break;
+ case kXMP_WAVFile:
+ fileFormatName = "WAV ";fileFormatDesc = "WAVE Form Audio Format";break;
+ case kXMP_MP3File:
+ fileFormatName = "MP3 ";fileFormatDesc = "MPEG-1 Audio Layer 3";break;
+ case kXMP_SESFile:
+ fileFormatName = "SES ";fileFormatDesc = "Audition session";break;
+ case kXMP_CELFile:
+ fileFormatName = "CEL ";fileFormatDesc = "Audition loop";break;
+ case kXMP_MPEGFile:
+ fileFormatName = "MPEG";fileFormatDesc = "Motion Pictures Experts Group";break;
+ case kXMP_MPEG2File:
+ fileFormatName = "MP2 ";fileFormatDesc = "MPEG-2";break;
+ case kXMP_WMAVFile:
+ fileFormatName = "WMAV";fileFormatDesc = "Windows Media Audio and Video";break;
+ case kXMP_HTMLFile:
+ fileFormatName = "HTML";fileFormatDesc = "HyperText Markup Language";break;
+ case kXMP_XMLFile:
+ fileFormatName = "XML ";fileFormatDesc = "Extensible Markup Language";break;
+ case kXMP_TextFile:
+ fileFormatName = "TXT ";fileFormatDesc = "text";break;
+ case kXMP_PhotoshopFile:
+ fileFormatName = "PSD ";fileFormatDesc = "Photoshop Document";break;
+ case kXMP_IllustratorFile:
+ fileFormatName = "AI ";fileFormatDesc = "Adobe Illustrator";break;
+ case kXMP_InDesignFile:
+ fileFormatName = "INDD";fileFormatDesc = "Adobe InDesign";break;
+ case kXMP_AEProjectFile:
+ fileFormatName = "AEP ";fileFormatDesc = "AfterEffects Project";break;
+ case kXMP_AEFilterPresetFile:
+ fileFormatName = "FFX ";fileFormatDesc = "AfterEffects Filter Preset";break;
+ case kXMP_EncoreProjectFile:
+ fileFormatName = "NCOR";fileFormatDesc = "Encore Project";break;
+ case kXMP_PremiereProjectFile:
+ fileFormatName = "PPRJ";fileFormatDesc = "Premier Project";break;
+ case kXMP_SWFFile:
+ fileFormatName = "SWF ";fileFormatDesc = "Shockwave Flash";break;
+ case kXMP_PremiereTitleFile:
+ fileFormatName = "PRTL";fileFormatDesc = "Premier Title";break;
+ case kXMP_UnknownFile:
+ fileFormatName = " ";fileFormatDesc = "Unkown file format";break;
+ default:
+ fileFormatName = " ";fileFormatDesc = "no known format constant";break;
+ }
+ } //fileFormatNameToString
+
+ /**
+ * GetFormatInfo-Flags (aka Handler-Flags)
+ * find this in XMP_Const.h under "Options for GetFormatInfo"
+ */
+ std::string XMPFiles_FormatInfoToString ( const XMP_OptionBits options) {
+ std::string outString;
+
+ if( options & kXMPFiles_CanInjectXMP )
+ outString.append(" CanInjectXMP");
+ if( options & kXMPFiles_CanExpand )
+ outString.append(" CanExpand");
+ if( options & kXMPFiles_CanRewrite )
+ outString.append(" CanRewrite");
+ if( options & kXMPFiles_PrefersInPlace )
+ outString.append(" PrefersInPlace");
+ if( options & kXMPFiles_CanReconcile )
+ outString.append(" CanReconcile");
+ if( options & kXMPFiles_AllowsOnlyXMP )
+ outString.append(" AllowsOnlyXMP");
+ if( options & kXMPFiles_ReturnsRawPacket )
+ outString.append(" ReturnsRawPacket");
+ if( options & kXMPFiles_HandlerOwnsFile )
+ outString.append(" HandlerOwnsFile");
+ if( options & kXMPFiles_AllowsSafeUpdate )
+ outString.append(" AllowsSafeUpdate");
+
+ if (outString.empty()) outString=" (none)";
+ return outString;
+ }
+
+ /**
+ * openOptions to String, find this in
+ * XMP_Const.h under "Options for OpenFile"
+ */
+ std::string XMPFiles_OpenOptionsToString ( const XMP_OptionBits options) {
+ std::string outString;
+ if( options & kXMPFiles_OpenForRead)
+ outString.append(" OpenForRead");
+ if( options & kXMPFiles_OpenForUpdate)
+ outString.append(" OpenForUpdate");
+ if( options & kXMPFiles_OpenOnlyXMP)
+ outString.append(" OpenOnlyXMP");
+ if( options & kXMPFiles_OpenStrictly)
+ outString.append(" OpenStrictly");
+ if( options & kXMPFiles_OpenUseSmartHandler)
+ outString.append(" OpenUseSmartHandler");
+ if( options & kXMPFiles_OpenUsePacketScanning)
+ outString.append(" OpenUsePacketScanning");
+ if( options & kXMPFiles_OpenLimitedScanning)
+ outString.append(" OpenLimitedScanning");
+
+ if (outString.empty()) outString=" (none)";
+ return outString;
+ }
+
+ //just the callback-Function
+ XMP_Status DumpToString( void * refCon, XMP_StringPtr outStr, XMP_StringLen outLen )
+ {
+ XMP_Status status = 0;
+ std::string* dumpString = static_cast < std::string* > ( refCon );
+ dumpString->append (outStr, outLen);
+ return status;
+ }
+
+// ********************************************************************************
+
+void Actions::version() {
+ XMP_VersionInfo coreVersion, filesVersion;
+ SXMPMeta::GetVersionInfo ( &coreVersion );
+ SXMPFiles::GetVersionInfo ( &filesVersion );
+ Log::info("%s", coreVersion.message);
+ Log::info("%s", filesVersion.message);
+ Log::info("Executable Version:%s", XMP_EXE_VERSION);
+
+#if XMP_WinBuild
+ Log::info("compiled on Windows on %s, %s",__DATE__,__TIME__);
+#endif
+#if XMP_MacBuild
+ Log::info("compiled on Mac on %s, %s",__DATE__,__TIME__);
+#endif
+#if NDEBUG
+ Log::info("Configuration: debug");
+#else
+ Log::info("Configuration: release");
+#endif
+ Log::info("Edition: Public SDK");
+#if XMPQE_BIG_ENDIAN
+ Log::info("Big endian machine");
+#elif XMPQE_LITTLE_ENDIAN
+ Log::info("Little endian machine");
+#else
+ Log::warn("unknown Endian !!!");
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+void Actions::info() {
+ if(!this->switch_nocheck) verifyFileIsReadable(this->mediafile);
+ XMP_FileFormat xmpFileFormat=kXMP_UnknownFile;
+ SXMPFiles xmpFile;
+
+ if ( !xmpFile.OpenFile(this->mediafile,xmpFileFormat,generalOpenFlags) )
+ Log::error("error opening file %s",this->mediafile.c_str());
+
+ std::string out_filepath;
+ XMP_OptionBits out_openFlags;
+ XMP_FileFormat out_fileFormat;
+ XMP_OptionBits out_handlerFlags;
+
+ if (!xmpFile.GetFileInfo(&out_filepath,&out_openFlags,&out_fileFormat,&out_handlerFlags))
+ Log::error("error doing GetFileInfo %s",this->mediafile.c_str());
+
+ //FilePath (just verify that identical to what's been used)
+ if ( strcmp(out_filepath.c_str(),this->mediafile.c_str()) ) //that's if it NOT matches (!=0 ...)
+ Log::warn("media filepath %s does not matches filepath %s obtained from GetFileInfo",
+ out_filepath.c_str(),this->mediafile.c_str());
+
+ //openOptions (also verify that identical to what's been used)
+ std::string openFlagsString=XMPFiles_OpenOptionsToString(out_openFlags);
+ Log::info("openFlags: %s (%X)", openFlagsString.c_str(), out_openFlags);
+ if ( generalOpenFlags != out_openFlags )
+ Log::warn("out_openFlags (0x%X) differ from those used for open (0x%X)",
+ out_openFlags,generalOpenFlags);
+
+ //FileFormat (resolves, usually by way of extension unless specified)
+ std::string fileFormatName, fileFormatDescription;
+ fileFormatNameToString(out_fileFormat,fileFormatName,fileFormatDescription);
+ Log::info("fileFormat: %s (%s,0x%X)",
+ fileFormatDescription.c_str(),fileFormatName.c_str(),out_fileFormat);
+
+ //FormatInfo aka "HandlerFlags" (show what is possible with this format)
+ std::string formatInfoString=XMPFiles_FormatInfoToString(out_handlerFlags);
+ Log::info("formatInfo:%s (0x%X)", formatInfoString.c_str(), out_handlerFlags);
+
+ xmpFile.CloseFile(generalCloseFlags); //(enabled again now that bug 1352603 is fixed)
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+void Actions::get() {
+ if(!this->switch_nocheck) verifyFileIsReadable(this->mediafile);
+ XMP_FileFormat xmpFileFormat=kXMP_UnknownFile;
+
+ SXMPFiles xmpFile;
+ std::string xmpDump; //really just the raw string here
+
+ if ( !xmpFile.OpenFile(this->mediafile,xmpFileFormat,generalOpenFlags) )
+ Log::error("error opening file %s",this->mediafile.c_str());
+
+ if ( this->switch_compact ) {
+ if ( !xmpFile.GetXMP(0,&xmpDump,0))
+ Log::warn("file contains no XMP. - says xmpFile.GetXMP()");
+ else
+ Log::info("%s",xmpDump.c_str());
+ } else {
+ SXMPMeta xmpMeta; //get meta-object first, then get serialization
+ if ( !xmpFile.GetXMP(&xmpMeta,0,0))
+ Log::warn("file contains no XMP. - says xmpFile.GetXMP()");
+ else {
+ xmpMeta.SerializeToBuffer(&xmpDump,
+ 0, //NOT using kXMP_UseCompactFormat - Use a highly compact RDF syntax and layout.
+ 0); //receiving string, options, 0(default) padding
+ Log::info("%s",xmpDump.c_str());
+ }
+ }
+
+ xmpFile.CloseFile(generalCloseFlags); //(enabled again now that bug 1352603 is fixed)
+}
+///////////////////////////////////////////////////////////////////////////////////////
+
+void Actions::dump() {
+ if(!this->switch_nocheck) verifyFileIsReadable(this->mediafile);
+ XMP_FileFormat xmpFileFormat=kXMP_UnknownFile;
+
+ SXMPFiles xmpFile;
+ std::string xmpDump; //really just the raw string here
+
+ if ( !xmpFile.OpenFile(this->mediafile,xmpFileFormat,generalOpenFlags) )
+ Log::error("error opening file %s",this->mediafile.c_str());
+
+ SXMPMeta xmpMeta; //get meta-object first, then get serialization
+ if ( !xmpFile.GetXMP(&xmpMeta,0,0))
+ Log::warn("file contains no XMP. - says xmpFile.GetXMP()");
+ else {
+ std::string dump;
+ xmpMeta.DumpObject(DumpToString, &dump);
+ Log::info( dump );
+ }
+
+ xmpFile.CloseFile(generalCloseFlags); //(enabled again now that bug 1352603 is fixed)
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////
+void Actions::put() {
+ //need read and write
+ if(!this->switch_nocheck) verifyFileIsWritable(this->mediafile);
+
+ //first open regular to see if injection possible
+ XMP_FileFormat xmpFileFormat=kXMP_UnknownFile;
+ SXMPFiles xmpFile;
+
+ if ( !xmpFile.OpenFile(this->mediafile,xmpFileFormat,generalOpenFlags | kXMPFiles_OpenForUpdate) )
+ Log::error("error opening file %s",this->mediafile.c_str());
+
+ std::string out_filepath;
+ XMP_FileFormat out_fileFormat;
+ XMP_OptionBits out_handlerFlags;
+
+ if (!xmpFile.GetFileInfo(0,0,&out_fileFormat,&out_handlerFlags))
+ Log::error("error doing GetFileInfo %s",this->mediafile.c_str());
+
+ bool foundXMP = xmpFile.GetXMP( 0, 0, 0);
+
+ //construct the to-be-injected snippet
+ std::string xmpMetaString=getStringFromFile(this->xmpsnippet);
+ SXMPMeta xmpMeta(xmpMetaString.c_str(),(XMP_StringLen)xmpMetaString.length());
+
+ // append metadata if we found XMP or if we didn't but can inject
+ if ( foundXMP == true || (out_handlerFlags & kXMPFiles_CanInjectXMP ) ) {
+
+ if ( !xmpFile.CanPutXMP(xmpMeta) )
+ Log::warn("canPutXMP denies I can inject: (xmpFile.CanPutXMP()==false)");
+
+ try {
+ xmpFile.PutXMP(xmpMeta); //void unfortunatley, must close file to figure out result...
+ xmpFile.CloseFile(generalCloseFlags); // close the file
+
+ } catch ( XMP_Error& e ) {
+ Log::error("puttingXMP failed: %s",e.GetErrMsg());
+ }
+ } else { //handle the error
+ //FileFormat (resolves, usually by way of extension unless specified TODO)
+ std::string fileFormatName, fileFormatDescription;
+ fileFormatNameToString(out_fileFormat,fileFormatName,fileFormatDescription);
+ Log::error("the File %s of type %s neither contains XMP nor can it be injected (at least kXMPFiles_CanInjectXMP not returned)",
+ this->mediafile.c_str(), fileFormatName.c_str());
+ };
+}
+
+void Actions::doAction() {
+ //do open and close-flags one for all (add ThumbFlag if needed above)
+ generalOpenFlags=kXMPFiles_OpenForRead;
+ if (this->switch_smart) generalOpenFlags|=kXMPFiles_OpenUseSmartHandler;
+ if (this->switch_scan) generalOpenFlags|=kXMPFiles_OpenUsePacketScanning;
+
+ generalCloseFlags=0;
+ if (this->switch_safe) generalOpenFlags|=kXMPFiles_UpdateSafely;
+
+ switch (this->actiontype){
+ case Actions::VERSION:
+ version();
+ break;
+ case Actions::INFO:
+ info();
+ break;
+ case Actions::PUT: //PutXMP()
+ put();
+ break;
+ case Actions::GET: //GetXMP()
+ get();
+ break;
+ case Actions::DUMP:
+ dump();
+ break;
+ default:
+ Log::error("unknown command or not implemented!!");
+ break;
+ }
+}
diff --git a/samples/source/xmpcommand/Actions.h b/samples/source/xmpcommand/Actions.h
new file mode 100644
index 0000000..f820f70
--- /dev/null
+++ b/samples/source/xmpcommand/Actions.h
@@ -0,0 +1,50 @@
+// =================================================================================================
+// Copyright 2006 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+#ifndef __ACTIONS_h__
+#define __ACTIONS_h__ 1
+
+class Actions {
+ public:
+ enum ACTION_TYPE { NONE, VERSION, INFO, PUT, GET, DUMP };
+
+ //the params to be set:
+ bool switch_safe; //asking for closing file with kXMPFiles_UpdateSafely (safe+rename that is)
+
+ //these two mututally exclusive:
+ bool switch_smart; /* Require the use of a smart handler. (kXMPFiles_OpenUseSmartHandler) */
+ bool switch_scan; /* Force packet scanning, don't use a smart handler. kXMPFiles_OpenUsePacketScanning */
+
+ bool switch_nocheck; /* no "sanity checks" on xmp-side i.e. for read/writeability, only usefull for testing "proper failure" */
+ bool switch_compact; /* ask extract to extract xmp in the compact (non-pretty) RDF-style (attributes rather than content of tags) */
+
+ std::string outfile;//output goes (besides stdout) to an output file...
+ std::string mediafile; //relative path to XMP snippet (null if none)
+
+ ACTION_TYPE actiontype; //inits with NONE
+
+ std::string xmpsnippet; //relative path to XMP snippet (null if none)
+
+ //distributes the actions to the different routines...
+ void doAction();
+
+private:
+ void version(void);
+ void info(void);
+ void put();
+ void get(void);
+ void dump(void);
+
+ XMP_OptionBits generalOpenFlags;
+ XMP_OptionBits generalCloseFlags;
+
+};
+
+#endif
diff --git a/samples/source/xmpcommand/PrintUsage.cpp b/samples/source/xmpcommand/PrintUsage.cpp
new file mode 100644
index 0000000..833721f
--- /dev/null
+++ b/samples/source/xmpcommand/PrintUsage.cpp
@@ -0,0 +1,84 @@
+// =================================================================================================
+// Copyright 2006 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+#include "samples/source/common/globals.h"
+#include "samples/source/common/Log.h"
+#include "samples/source/xmpcommand/PrintUsage.h"
+
+using namespace std;
+
+namespace XMPQE {
+
+ void PrintUsageShort(char* exename) {
+ Log::info("%s -version",exename);
+ Log::info(" Print version information for this utility");
+ Log::info("");
+ Log::info("%s -help",exename);
+ Log::info(" Print descriptions of all available switches, including examples");
+ Log::info("");
+ Log::info("%s [switch [switch]...] action mediafile",exename);
+ Log::info("");
+ Log::info("This command uses the XMPFiles component of the Adobe XMP toolkit. ");
+ Log::info("Copyright 2008 Adobe Systems Incorporated. All Rights Reserved.");
+ Log::info("");
+ }
+
+ void PrintUsageLong(char* exename) {
+ Log::info("%s -version",exename);
+ Log::info(" Print version information for this utility");
+ Log::info("%s -help",exename);
+ Log::info(" Print descriptions of all available switches, including examples");
+ Log::info("%s [switch [switch]...] action mediafile",exename);
+ Log::info("This command uses the XMPFiles component of the Adobe XMP toolkit. ");
+ Log::info("Copyright 2008 Adobe Systems Incorporated. All Rights Reserved.");
+ Log::info("NOTICE: Adobe permits you to use, modify, and distribute this file in");
+ Log::info(" accordance with the terms of the Adobe license agreement");
+ Log::info(" accompanying it.");
+ Log::info("");
+ Log::info("Switches:");
+ Log::info("-out <outfile> Writes output and logs all warnings and errors both to ");
+ Log::info(" standard output, and also to the specified output file. ");
+ Log::info(" If you specify the output file without this switch, stdout is not used.");
+ Log::info(" You should check that there are no warnings or errors (return value is 0) ");
+ Log::info(" before using the output; stderr is not used.");
+ Log::info("-safe Updates safely, writing to a temporary file then renaming ");
+ Log::info(" it to the original file name. See API documentation for ");
+ Log::info(" safeSave(kXMPFiles_UpdateSafely).");
+ Log::info("-smart Requires the use of a smart file-format handler, does no packet scanning.");
+ Log::info(" Use of smart handlers is the default, if one is available.");
+ Log::info("-scan Forces packet scanning, does not use a smart file-format handler.");
+ Log::info("-nocheck Omits readability/writeability checks. Used internally. ");
+ Log::info("");
+ Log::info("-compact Writes extracted XMP Packet in compact RDF-style, rather than");
+ Log::info(" pretty-printing attribute value for readability (which is the default).");
+
+ Log::info("");
+ Log::info("Actions:");
+ Log::info(" info <mediafile> Prints basic information about the file.");
+ Log::info(" put <xmpfile> <mediafile> Injects the XMP contained in the specified xmpfile ");
+ Log::info(" into the specified mediafile.");
+ Log::info(" get <mediafile> Retrieves the XMP Packet contained in the specified mediafile.");
+ Log::info(" dump <mediafile> Prints the XMP Packet contained in the specified mediafile to standard output. ");
+ Log::info(" <<??>> USE dump ONLY for known-good-output tests, do not use get. <<??>> ");
+ Log::info("");
+ Log::info("Examples:");
+ Log::info("%s info Sample.jpg",exename);
+ Log::info("%s get ../Sample.jpg >onlyFileOut.txt",exename);
+ Log::info("%s -out alsoFileOut.txt get Sample.eps",exename);
+ Log::info("%s put xmp_mySnippet.xmp Sample.jpg",exename);
+ Log::info("%s -smart put xmp_mySnippet.xmp Sample.jpg",exename);
+ Log::info("(Smart handler would be used by default for a JPEG file even without the switch.)");
+ Log::info("");
+ Log::info("Returns 0 if there are no errors. Warnings and errors are printed as part of the output. ");
+ Log::info("You should check the return value before using the output.");
+ Log::info(" ");
+ }
+
+} // of namespace XMPQE
diff --git a/samples/source/xmpcommand/PrintUsage.h b/samples/source/xmpcommand/PrintUsage.h
new file mode 100644
index 0000000..4d5b7a6
--- /dev/null
+++ b/samples/source/xmpcommand/PrintUsage.h
@@ -0,0 +1,19 @@
+// =================================================================================================
+// Copyright 2006 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+#ifndef __XMPQE_PRINT_USAGE_h__
+#define __XMPQE_PRINT_USAGE_h__ 1
+
+namespace XMPQE {
+ void PrintUsageShort(char* exename);
+ void PrintUsageLong(char* exename);
+}
+
+#endif
diff --git a/samples/source/xmpcommand/XMPCommand.cpp b/samples/source/xmpcommand/XMPCommand.cpp
new file mode 100644
index 0000000..5ab4fdb
--- /dev/null
+++ b/samples/source/xmpcommand/XMPCommand.cpp
@@ -0,0 +1,218 @@
+// =================================================================================================
+// Copyright 2005 Adobe
+// All Rights Reserved.
+//
+// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
+// of the Adobe license agreement accompanying it. If you have received this file from a source other
+// than Adobe, then your use, modification, or distribution of it requires the prior written permission
+// of Adobe.
+// =================================================================================================
+
+/**
+* A command-line tool for performing basic XMP actions such as get, put and dump. Can be used for testing
+* and scripting automation.
+*/
+#ifdef WIN32
+ #pragma warning ( disable : 4267 ) // suppress string conversion warning
+#endif
+
+#include <stdexcept>
+#include <iostream>
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <cstring>
+#include <sstream>
+
+#include "samples/source/common/globals.h"
+#include "samples/source/common/Log.h"
+#include "samples/source/common/LargeFileAccess.hpp"
+//some global constants / sanity checking
+
+
+#define EXENAME "xmpcommand"
+
+//XMP related
+#define TXMP_STRING_TYPE std::string
+#define XMP_INCLUDE_XMPFILES 1
+#include "public/include/XMP.hpp"
+#include "public/include/XMP.incl_cpp" //include in EXACTLY one source file (i.e. main, in Action gets you trouble...)
+
+//QE related
+#include "samples/source/xmpcommand/Actions.h"
+// -help output
+#include "samples/source/xmpcommand/PrintUsage.h"
+
+
+namespace XMPQE {
+ void InitDependencies()
+ {
+ if ( ! SXMPMeta::Initialize() )
+ Log::error( "XMPMeta::Initialize failed!" );
+
+ XMP_OptionBits options = 0;
+ #if UNIX_ENV
+ options |= kXMPFiles_ServerMode;
+ #endif
+
+ if ( ! SXMPFiles::Initialize ( options ) )
+ Log::error( "XMPFiles::Initialize failed!" );
+ }
+
+ void ShutdownDependencies()
+ {
+ SXMPFiles::Terminate();
+ SXMPMeta::Terminate();
+ }
+} //namespace XMPQE
+
+using namespace XMPQE;
+
+int main ( int argc, const char * argv[] )
+{
+ Actions action; //create Action (to fill up with parameters below...)
+
+ // ============================================================================================
+ // ============================================================================================
+ // ============================================================================================
+ try { // on try for all
+
+ //clean parameters (this is not elegant but might avoid some macPPC-side bus error bugs whatsoever
+ action.outfile="";
+ action.switch_safe=false;
+ action.switch_smart=false;
+ action.switch_scan=false;
+ action.switch_nocheck=false;
+ action.switch_compact=false;
+ action.mediafile="";
+ action.actiontype=Actions::NONE;
+ action.xmpsnippet="";
+
+ ////////////////////////////////////////////
+ // parameter verification
+ if ( argc == 1 ) { //aka no parameters, just command itself.
+ XMPQE::PrintUsageShort(EXENAME);
+ return 0;
+ } else if ( argc == 2 ) //single-paramter-call?
+ {
+ if ( !strcmp("help",argv[1]) || //only "help" and "version" permitted, ...
+ !strcmp("-help",argv[1]) ||
+ !strcmp("--help",argv[1]) ||
+ !strcmp("-h",argv[1]) ||
+ !strcmp("-?",argv[1]) ||
+ !strcmp("/?",argv[1]) ) {
+ XMPQE::PrintUsageLong(EXENAME);
+ return 0;
+ }
+ else if ( !strcmp("-version",argv[1]) || !strcmp("version",argv[1]) )
+ {
+ action.actiontype=Actions::VERSION;
+ }
+ else
+ { //..thus fail anything else
+ Log::error("unknown parameter %s",argv[1]);
+ }
+
+ }
+ else //multi-argument parameters
+ {
+ int i=1; // "for (int i=1;i<argc;i++)" ....
+
+ while (true) {
+ if (argv[i][0] != '-') {
+ break; //apparently done parsing switches
+ }
+ if ( !strcmp("-safe",argv[i])) {
+ action.switch_safe=true;
+ }
+ else if ( !strcmp("-smart",argv[i])) {
+ action.switch_smart=true;
+ }
+ else if ( !strcmp("-nocheck",argv[i])) {
+ action.switch_nocheck=true;
+ }
+ else if ( !strcmp("-compact",argv[i])) {
+ action.switch_compact=true;
+ }
+ else if ( !strcmp("-scan",argv[i])) {
+ action.switch_scan=true;
+ } else if ( !strcmp("-out",argv[i])) {
+ i++; //ok, so gimme that file
+ if (i>=argc) Log::error("missing output parameter, etc.");
+ action.outfile=argv[i];
+ } else
+ Log::error("unknown switch %s",argv[i]);
+ i++;
+ if (i>=argc) Log::error("missing action parameter, etc.");
+ }
+
+ //check mutually exclusive
+ if (action.switch_scan && action.switch_smart)
+ Log::error("use either -smart or -scan");
+
+ if ( !strcmp("info",argv[i])) {
+ action.actiontype= Actions::INFO;
+ } else if ( !strcmp("put",argv[i]) || !strcmp("inject",argv[i]) ) {
+ action.actiontype= Actions::PUT;
+ } else if ( !strcmp("get",argv[i]) || !strcmp("extract",argv[i]) ) {
+ action.actiontype= Actions::GET;
+ } else if ( !strcmp("dump",argv[i])) {
+ action.actiontype= Actions::DUMP;
+ } else
+ Log::error("_unknown action %s",argv[i]);
+ i++;
+
+ //only inject needs an in-file parameter really...
+ if ( action.actiontype== Actions::PUT) {
+ if (i>=argc) Log::error("missing <xmpfile> parameter");
+ action.xmpsnippet=argv[i];
+ i++;
+ }
+
+ //last argument must be mediafile
+ if (i>=argc) Log::error("missing mediafile parameter");
+ action.mediafile=argv[i];
+
+ i++;
+ if (i!=argc) Log::error("too many parameters.");
+ }
+
+ //Init ===========================================================================
+ InitDependencies();
+
+ //Do Action //////////////////
+ Log log(action.outfile.c_str()); //will start logging to file (if filename given) or not ("")
+ action.doAction();
+
+ // Shutdown /////////////////////////////////////////////
+ ShutdownDependencies();
+
+ }
+ catch (XMP_Error& e ) {
+ Log::info("Unexpected XMP_Error %s\n", e.GetErrMsg()); //throw no further, no Log::error.. !
+ return -2;
+ }
+ catch (LOG_Exception& q) {
+ Log::info("LOG_Exception:%s\n",q.what());
+ return -3;
+ }
+ catch (std::runtime_error& p) {
+ Log::info("caught std::runtime_error exception: %s\n",p.what());
+ return -4;
+ }
+ catch (...) {
+ Log::info("unidentified error on action execution\n");
+ return -5;
+ }
+
+ // ============================================================================================
+ // ============================================================================================
+ // ============================================================================================
+
+ if (Log::noErrorsOrWarnings)
+ return 0;
+ //else
+ Log::info("%s finished with warnings",EXENAME);
+ return 10;
+}
+