diff options
author | Priyanka-Gupta <gupta66priyanka@gmail.com> | 2020-01-29 10:33:16 +0530 |
---|---|---|
committer | Priyanka-Gupta <gupta66priyanka@gmail.com> | 2020-01-29 10:33:16 +0530 |
commit | 0872e35a30457d2ecf746a1bebdb7d94636d0e2f (patch) | |
tree | b62c5636ca3955e9b2bb8631e7dc351e41d14912 /samples/source | |
parent | 8c4ca18c6dc28bf3774acb399bf6ee680985bf50 (diff) |
XMP Toolkit SDK Jan 2020
Diffstat (limited to 'samples/source')
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 
 CR</ns1:HasCR>" + " <ns1:HasLF>ASCII 
 LF</ns1:HasLF>" + " <ns1:HasCRLF>ASCII 
 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, ¶ms); +#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, ¶ms); +#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, ¶ms); +#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; +} + |