Tag Archives: programming

The Chameleon Pathnames

The title might be as well “When the pathname is not what it has to be”.

The experience of developing plugins for Adobe Acrobat/Reader reserved me different surprises, that made the task more challenging. One of the biggest surprises I had was the impact of the Adobe’s Cloud idea over the Acrobat’ API within Acrobat products. Their feature idea is to keep all the already opened documents within their Cloud in order to make them available to different devices you’re interacting with.

In my case, having interactions with external non-Adobe’s applications, the things complicated when trying to get the file pathname. This option is coming enabled by default.

This is how the Acrobat.com Cloud looks like within Acrobat products This is how the Acrobat.com Cloud looks like within Acrobat products

Usually, when we are thinking to files path we expect to have something similar to GetFullPathName(). But according to Acrobat SDK’s concept: not every file opened into Acrobat/Reader has to be a local disk file. It may be associated with a stream, a network file, etc.

The reason why I was looking to get the correct file path is that my plugin and others are connecting to a system that expects the local or network file path. So it was needed to find a way to get a usual file path.

The challenge I am talking about has reproduced with an Adobe.com environment activated, having such a file already synchronized in the Acrobat.com cloud by using:

acrobat.com_path_functions

But both API’s functions return proper values with non-Cloud files. With a local filename not already uploaded within Acrobat.com I got the correct file path with both functions.

So the workaround I was thinking invokes the next steps:

1. Get the file path using ASFileSysDIPathFromPath(). In case your project is a Unicode project don’t forget that the returned type is a char* and you’ll need to encode it to the proper Unicode (UTF-8 in my case).

AVDoc avDoc = AVAppGetActiveDoc();
if (NULL == avDoc)  // no doc is loaded
{
  char strErrorMsg[MAX_PATH] = {0};
  strcat_s(strErrorMsg, "There is no opened file.");
  AVAlertNote(strErrorMsg);
  return;
}
PDDoc pdDoc = AVDocGetPDDoc(doc);
ASFile fileinfo = PDDocGetFile(pdDoc);
ASFileSys fileSys = ASFileGetFileSys(fileinfo);
ASPathName pathname = ASFileAcquirePathName(fileinfo);
char* szFilePath = ASFileSysDIPathFromPath(fileSys, pathname, pathname);
// this declaration it's just for sample
// szFilePath = "/Acrobat.com/
// 89323c47-676e-44a3-9fc3-cd18fe57bc91/3ac312be-c13d-4a00-bd57-b454a52019e3/myFile.pdf"
char* szAnsiPath = ASFileSysDisplayStringFromPath(fileSys, pathname);
auto strTmpPath = std::vector<wchar_t>{0};  // get the length of the path
int result =
    MultiByteToWideChar(CP_UTF8, NULL, szAnsiPath, strlen(szAnsiPath), NULL, 0);
if (result > 0) {
  strTmpPath.resize(result + 1);  // encode into UTF-8 the array
  result = MultiByteToWideChar(CP_UTF8, NULL, szAnsiPath, strlen(szAnsiPath),
                               &strTmpPath[0], result);
  strTmpPath[result] = '\0';
  if (result == 0)
    TRACE_ERROR(
        _T("The transformation ANSI to UNICODE has failed. Error code: %d"),
        GetLastError());
}
tstring sFilePath(&strTmpPath[0]);  // continue by using the sFilePath

2. Check if it is a cloud path (starts with Acrobat.com:).

if (ptr4Cloud && ptr4Cloud->checkCloudPath(sFilePath)) {
  tstring newFileGeneratedPath;
  ptr4Cloud->SaveFileOnDisk(
      pdDoc, sFilePath,
      newFileGeneratedPath);  // use the newFileGeneratedPath as expected 
}

where

bool FooCloud::checkCloudPath(const ustring& strCloudPath) {
  if (m_cloudPrefix.length() > strCloudPath.length()) {
    return false;
  }
  return (std::mismatch(m_cloudPrefix.begin(), m_cloudPrefix.end(),
    strCloudPath.begin())
    .first == m_cloudPrefix.end());
}

3. Save the file content into a temporary file (ex. C:\Users\Silviu.Ardelean\AppData\Local\Temp\)

void FooCloud::SaveFileOnDisk(PDDoc pdDoc,
                              const tstring& strCloudPath,
                              tstring& newFilePath) {
  DWORD dwRetVal = 0;
  TCHAR lpTempPathBuffer[MAX_PATH + 1] = {0};
  tstring fileName = getFileName(strCloudPath);
  dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
  if (dwRetVal != 0)
    newFilePath = lpTempPathBuffer;
  newFilePath += (!fileName.empty() && dwRetVal != 0)
                     ? fileName
                     : _T("YourFile.pdf");  // create the effective file
  ASFileSys fileSysX = ASGetDefaultFileSys();
  ASPathName new_path =
      ASFileSysCreatePathName(fileSysX, ASAtomFromString("Cstring"),
                              (const void*)CW2A(newFilePath.c_str()), NULL);
  PDDocCopyParams saveParams =
      (PDDocCopyParams)ASmalloc(sizeof(PDDocCopyParamsRec));
  saveParams->size = sizeof(PDDocCopyParamsRec);
  saveParams->newPath = new_path;
  saveParams->fileSys = fileSysX;
  saveParams->cancelProc = NULL;
  saveParams->cancelProcData = NULL;
  saveParams->progMon = NULL;
  saveParams->progMonData = NULL;
  saveParams->saveChanges = false;
  PDDocCopyToFile(pdDoc, saveParams);
  m_listFiles.push_back(newFilePath);
  ASfree(saveParams);
  ASFileSysReleasePath(fileSysX, new_path);
}

where

tstring FooCloud::getFileName(const tstring& strCloudPath) {
  return (strCloudPath.length() < m_cloudPrefix.length())
    ? _T("")
    : strCloudPath.substr(m_cloudPrefix.length());
}

4. Provided the temporary file path to the proxy module that expects it to interact with my system.

5. Clean/delete the temporary files on plugin uploading – PluginUnload() callback.

Additional comments
In case your plugins will interact with external non-Adobe’s application most probably you’ll have to do different tricks. Because of the way the Acrobat SDK is designed, without direct support for wchar_t and std::wstring you will need to make different conversions and encoding/decoding (ex. the ATL macros CW2A, CA2W(), functions such MultiByteToWideChar() on Windows, etc).

If you don’t have to interact with external non-Adobe’s applications be confident with Acrobat’s SDK types and data structures. In this way, you’ll avoid such conversions.

HTML files generation using XML and XSLT with Microsoft XML DOM API

This short tutorial shows how easy it’s to generate reports in HTML pages using Microsoft XML DOM API together XML and XSLT.

XML (Extensible Markup Language) became a universal standard of encoding data in a format that is both human-readable and machine-readable. It’s widely used in business applications and even Microsoft Office uses it into internal file formats.
XSLT is used for XML documents decoration. Once we have data into a XML files, using the XSLT (Extensible Stylesheet Language Transformations) we can easily generate HTML and xHTML files. XSLT is a W3C recommendation still from 16. November 1999 and in the meantime, it was extended with a new version XSLT v2.0.

XSLT uses XPATH to get the XML’s tags information, complete the predefined temples and transform results into a .html document.
Each decent browser has support for XML and XSLT. All we have to do it’s to link two such files (.xml and .xslt) and once we execute the XML file the browser will generate and render our XHTML content.

// content of XML file

<!--?xml version="1.0" encoding="utf-8"?-->
<!--?xml-stylesheet href="sample_1.xslt" type="text/xsl"?-->

// the rest of the XML file

But in case we are writing non-browser applications the HTML generation becomes a bit complicated in case you are not satisfied with a hard-coded solution and want a flexible solution.

Using the Microsoft’s XML Core Services (MSXML) our job became a piece of cake. We focus once over the HTML generator and later in case we want to change something into our look and content we have to deal only with the .xml and .xslt files.

bool CHTMLGen::Generate(const std::wstring& sXmlFile,
                        const std::wstring& sXsltFile,
                        const std::wstring& sHTMLFile) {
  if (!PathFileExists(sXmlFile.c_str()) || !PathFileExists(sXsltFile.c_str())) {
    return false;
  }

  HRESULT hr;
  CComPtr pXml, pXslt;

  hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,
                        IID_IXMLDOMDocument, (void**)&pXml);
  if (FAILED(hr))
    return false;

  VARIANT_BOOL bOkLoad;
  CComVariant varFile;

  varFile = sXmlFile.c_str();
  pXml->put_async(VARIANT_FALSE);
  hr = pXml->load(varFile, &bOkLoad);
  if (FAILED(hr) && (bOkLoad == VARIANT_FALSE))
    return false;

  hr = CoCreateInstance(CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER,
                        IID_IXMLDOMDocument, (void**)&pXslt);
  if (FAILED(hr))
    return false;

  varFile = sXsltFile.c_str();
  pXslt->put_async(VARIANT_FALSE);
  hr = pXslt->load(varFile, &bOkLoad);
  if (FAILED(hr) && (bOkLoad == VARIANT_FALSE))
    return false;

  CComBSTR bsHtmlRes;
  std::wstring sHTMLRes = _T("");
  hr = pXml->transformNode(pXslt, &bsHtmlRes);
  if (SUCCEEDED(hr)) {
    CAtlString sRes(bsHtmlRes);
    if (!_tcsnicmp(sRes, _T(""));
      sHTMLRes += szEnd ? (szEnd + 2) : sRes;
  } else
    sHTMLRes += sRes;
 }

 return (_T("") != sHTMLRes) ? SaveFileContent(sHTMLFile, sHTMLRes) : false;
}

Because of using COM don’t forget proper the calls of CoInitialize() and CoUninitialize().
Here are two samples files generated with the test application using the upper method: sample_1, sample_2.

The combination of XML, XSLT and XPATH offers a very flexible way to generate HTML files. With such an approach even native application does not need to change in case we change the HTMLs look. Within the presented case the hard-coded solutions are avoided and most probably a new recompilation is not needed in case we want to change data content (XML) or the look (XSLT).
In case you want to add sophisticated HTML code (ex. colored, formatted, images, etc.) you need to convert that code into XHTML format before adding data into the .XSLT file.

demo application (3005 downloads)

Versionable Object’s Serialization using MFC in non Document View applications

Most existing applications operate with data that must be stored and loaded in different times and different locations. The data is stored in text or binary files with a well defined format.
The Problem
Initially, in the first version 1.0, an application operates with data structures that can be stored and loaded. But, next version (2.0) these data structures suffers changes. Some structure’s attributes are added and other could be removed. These things change files format and structure when a new file version is saved.
Question: What happens when you are using an application version 2.0 and you are trying to load old files format (version 1.0)?
Answer: Most cases should cause incompatibility troubles between the current new application and files in old format. This could throw exceptions and the application could have undefined behavior.
That’s why the application must be written in order to be able to open both file versions.
Solution
In order to solve the compatibility issue exist many solutions, more or less professional. The recommended solution is the serialization.
The serialization is a write / read object process to/from a persistent storage. Serialization it’s a good choice in order to maintain a good data structure. Many different frameworks offer serialization support. One of these is Microsoft Foundation Classes – MFC.

If we want to use MFC serialization support, we can use a CArchive instance. This object, combined with a CFile instance provides a strong mechanism for objects serialization.

Because, in different applications version, the file suffers significant structure changes we have to use MFC’s serialization concept called Versionable Schema.
Versionable Schema means the using of CArchive class methods GetObjectSchema() and SetObjectSchema() and a constant VERSIONABLE_SCHEMA (that you can find it in afx.h file and has 0x80000000 value) combined with a OR LOGIC operator and the last application version number as a parameter of IMPLEMENT_SERIAL macro.
The GetObjectSchema() method is used in order to detect stored objects version from a file that is loaded in our application. The complement of this method, SetObjectSchema() method, allow us to save the objects version.
Different by the C++ I/O standard streams, the CArchive class is special designed only for objects serialization in binary files.

In order to serialize a class’s objects we have to follow next steps:
1. The class that we want to serialize has to be derived from the abstract class CObject (or other classes derived from CObject).
2. Overwrite CObject’s Serialize() method.
3. Use DECLARE_SERIAL macro in your class declaration.
4. The serializable class has to have a default constructor, without arguments.
5. Use IMPLEMENT_SERIAL macro in the implementation file of serializable class.

More information you can find it here and in links of this page.

But, from these steps until to a complete serialization and versionable application there are few significant steps to follow.
Next, I will present to you a Dialog base sample application that supports serialization and is versionable.

Sample application – SerAddressBook

Next, I will present to you how you can create an address book application (based on a MFC Dialog application architecture).

Suppose that initially our client requested us an address book that contaions: name, prename, address and phone number. But, once with the mobile phone and Internet area extensions our client needs two new fields for mobile phone number and for email address.
File versions structure

Our application with a file version 2 looks like this:
Application window

Because this is a demo application I kept on my window the possibility to save both version, using two radio buttons.
A good application design helps us if we have new requirements and we have to change the application structure. The code changes have to be done without too many code interactions. Ideally, with add code only.
That’s why, my application classes design looks like this:
Classes Hierarchy

Although Contact class and CAddressBook class are serializable, the objects serialization is implemented into Contact class.

Contact class

From Contact’s interface class you can observe:
• I derived this class from the abstract CObject;
DECLARE_SERIAL, macro calling;
Serialize()‘s method declaration in order to overwrite the parent class;
• Our class attributes.

class Contact : public CObject
{
public:
  DECLARE_SERIAL(Contact);

  Contact();
  Contact(const Contact& rhs);
  Contact(const CString& strFirstName, const CString& strLastName, const CString& strAddress, const CString& strPhone,
  // these two must have a default value because they are not used in the first version
  const CString& strMobilePhone = _T(""), const CString& strEmail = _T(""));
  Contact& operator=(const Contact& rhs);

  virtual ~Contact();

  void Serialize( CArchive& ar );

  CString GetFirstName() const {return m_strFirstName;}
  CString GetLastName() const {return m_strLastName;}
  CString GetAddress() const {return m_strAddress;}
  CString GetPhoneNumber() const {return m_strPhone;}
  CString GetMobileNumber() const {return m_strMobilePhone;}
  CString GetEmail() const {return m_strEmail;}

  void SetFirstName(const CString& name) {m_strFirstName = name;}
  void SetLastName(const CString& name) {m_strLastName = name;}
  void SetAddress(const CString& addr) {m_strAddress = addr;}
  void SetPhoneNumber(const CString& phone) {m_strPhone = phone;}
  void SetMobileNumber(const CString& mobile) {m_strMobilePhone = mobile;}
  void SetEmail(const CString& email) {m_strEmail = email;}

  static void SetCurrentFileVersion(int nCV) { CURRENT_VERSION = nCV; }
  static int GetCurrentFileVersion() { return CURRENT_VERSION; }

private:
  static int CURRENT_VERSION;
  CString m_strFirstName;
  CString m_strLastName;
  CString m_strAddress;
  CString m_strPhone;
  CString m_strMobilePhone;
  CString m_strEmail;
};

typedef CList<contact, contact=""> ContactList;

The last line represents an “alias” definition for a MFC list definition, used in order to store displayed data. This list is using for Contact object administration.
In the implementation file we declare the IMPLEMENT_SERIAL macro and we are initializing the static variable with our current application version.

IMPLEMENT_SERIAL( Contact, CObject, VERSIONABLE_SCHEMA | 2);
int Contact::CURRENT_VERSION = 2;

Into this declaration you can observe the VERSIOABLE_SCHEMA constant combined on OR logic with 2 (my demo application last version). This, the third macro argument is essential for objects versioning, combined with CArchive::GetObjectSchema() and CArchive::SetObjectSchema().
More details about this constant and it using process or about these methods you can find here.
The implementation of Contacte::Serialize() looks like this:

void Contact::Serialize( CArchive& ar )
{
  if (ar.IsStoring())
  {
    CRuntimeClass* pruntime = Contact::GetRuntimeClass();
    int oldnr = pruntime->m_wSchema;
    pruntime->m_wSchema = CURRENT_VERSION;

    ar.SerializeClass(pruntime);

    switch (CURRENT_VERSION)
    {
      case 1:
        ar << m_strFirstName << m_strLastName << m_strAddress << m_strPhone;
      break; 
      
      case 2: 
        ar << m_strFirstName << m_strLastName << m_strAddress << m_strPhone << m_strMobilePhone << m_strEmail;
      break;
      
      default: // unknown version for this object 
        AfxMessageBox(_T("Unknown file version."), MB_ICONSTOP); 
        break; 
    } pruntime->m_wSchema = oldnr;
  } else // loading code
    {
      ar.SerializeClass(RUNTIME_CLASS(Contact));

      UINT nVersion = ar.GetObjectSchema();

      switch (nVersion)
      {
        case 1:
        ar >> m_strFirstName >> m_strLastName >> m_strAddress >> m_strPhone;
        m_strMobilePhone = _T("");
        m_strEmail = _T("");
        break;

        case 2:
        ar >> m_strFirstName >> m_strLastName >> m_strAddress >> m_strPhone >> m_strMobilePhone >> m_strEmail;
        break;

        default:
        // unknown version for this object
        AfxThrowArchiveException(CArchiveException::badSchema);
        break;
     }
   }
}

If CArchive constructor sets store-load flag on CArchive::store (save to file) then the code flow will follow true if’s block and objects data is sending to archive and stored in the file (including file version, too).
When we want to open an existing file, our CArchive’s constructor receives CArcuhive::load flag and enter to else if’s block of Serialize(). Is extracting file version, and after that is loading all Contact objects.

CAddressBook class

CAddressBook class make the link between interface dialog class (CSerAddressBookDlg) and the serialized class Contact. This class contains a Contact objects list. CAddressBook class is administrating this contact list and is realizing load/store object.

The interface of this class is looking like this:

class CAddressBook : public CObject
{
  DECLARE_SERIAL(CAddressBook);
  ContactList m_cContactsList;

  public:
    CAddressBook();
    virtual ~CAddressBook();

    void Serialize( CArchive& ar );

    bool AddContact(const Contact& contact);
    bool RemoveContact(const CString& firstname,
    const CString& lastname);
    POSITION FindContact(const CString& firstname) const;
    bool FindContact(const CString& firstname, Contact& contact) const;

    const ContactList& GetContacts() const {return m_cContactsList;}

    void SetFileVersion(int nFV) { m_uiFileVersion = nFV; }
    int GetFileVersion() { return m_uiFileVersion; }

  private:
    int m_uiFileVersion;
};

Into this declaration you can see the existence of a contact list instance (m_cContactsList). This class contains add, update or remove contact methods.

Because our class has to be a serialized method we have to overwrite Serialize() method, the method has to me used by the client class ( in our case the interface class – CSerAddressBookDlg).

void CAddressBook::Serialize(CArchive& ar)
{
  ar.SerializeClass(RUNTIME_CLASS(CAddressBook));

  // storing
  if (ar.IsStoring())
  {
    ar << m_uiFileVersion; // write the number of contacts 
    ar << (int)m_cContactsList.GetCount(); 
    Contact::SetCurrentFileVersion(m_uiFileVersion);

    // write all the contacts 
    POSITION pos = m_cContactsList.GetHeadPosition();
    while(pos != NULL) { 
      Contact contact = m_cContactsList.GetNext(pos); 
      contact.Serialize(ar); 
    } 
  } else // loading 
  { ar >> m_uiFileVersion;
    m_cContactsList.RemoveAll();
    int count = 0;
    ar >> count;
    
    // read the number of contacts
    for(INT_PTR i = 0; i < count; ++i) 
    { 
      Contact contact;
      contact.Serialize(ar);
      m_cContactsList.AddTail(contact);
    } 
  } 
}

Because CArchive class doesn’t provides any method or attribute in order to obtain the objects (I’m counting when I’m loading or storing), I decided to save the objects count into my files. That’s why, if I’m storing, I call next line:

ar << m_cContactsList.GetCount();

Same story, for file version, before starting Contact objects serialization:

ar << m_uiFileVersion;

Then, into a while loop I’m iterating over the contact list. I am serializing the data and I’m storing to my new file. If I load a file from disk (else branch) then I follow next steps:
• I’m cleaning contact list;
• Get the objects count;
• Get file version and serialize all objects for load;
• Add all data to my Contact object list;

CSerAddressBookDlg

– The application interface class

Once we have implemented this serialization mechanism the using of this one into our application became very easy.

For instance, when the user wants to save into a file all he’s new data, he will call next method:

void CSerAddressBookDlg::SaveDataContentToFile(CString strSaveFile)
{
  CFile wFile(strSaveFile, CFile::modeCreate | CFile::modeWrite);

  // Create a storing archive
  CArchive arStore(&wFile, CArchive::store);
  m_c_AddressBook.Serialize(arStore);

  // Close the storing archive
  arStore.Close();

  PopulateList();
}

As you can see, I have a CFile object that I’m using it, combined with a CArchive instance, for data storing to a file. Although my local CArchive instance receive as a first parameter the address of the file handler and the store flag CArchive::store.
Next I call CAddressBook::Serialize() method and I’m closing the store operation.

Loading file method, based on my Contact serialization mechanism looks like this:

void CSerAddressBookDlg::LoadDataContentFromFile(CString strLoadedFile)
{
  CFile rFile(strLoadedFile, CFile::modeRead);

  // Create a loading archive
  CArchive arLoad(&rFile, CArchive::load);
  m_c_AddressBook.Serialize(arLoad);

  // Close the loading archive
  arLoad.Close();

  switch (m_c_AddressBook.GetFileVersion())
  {
    case 1:
    ((CButton*)GetDlgItem(IDC_RADIO_VERSION_1))->SetCheck(BST_CHECKED);
    ((CButton*)GetDlgItem(IDC_RADIO_VERSION_2))->SetCheck(BST_UNCHECKED);
    OnBnClickedRadioVersion1();
    break;
    case 2:
    ((CButton*)GetDlgItem(IDC_RADIO_VERSION_1))->SetCheck(BST_UNCHECKED);
    ((CButton*)GetDlgItem(IDC_RADIO_VERSION_2))->SetCheck(BST_CHECKED);
    OnBnClickedRadioVersion2();
    break;
    default:
    break;
  }

  // repopulate the list
  PopulateList();
}

As you can see, I am creating a local CFile object, needed for reading operation. Although, I’m creating a local CArchive instance that received as constructor parameter the file handler address with CArchive::load flag.
Then, I’m calling CAddressBook::Serialize() method. Is entering on else branch and finally we are disconnected the object from file.
The last line contains PopulateList() call and is my populate list method. It populates my list control (a CListCtrl instance) with the file loaded data in order to display it into our dialog.

void CSerAddressBookDlg::PopulateList()
{
  // delete all current members
  m_cList.DeleteAllItems();

  // get a reference to the contacts list
  const ContactList& contacts = m_c_AddressBook.GetContacts();

  // iterate over all contacts add add them to the list
  int nCurrentItem = 0;
  POSITION pos = contacts.GetHeadPosition();
  while(pos != NULL)
  {
    const Contact contact = contacts.GetNext(pos);

    nCurrentItem = m_cList.InsertItem(nCurrentItem, contact.GetFirstName());
    m_cList.SetItemText(nCurrentItem, 1, contact.GetLastName());
    m_cList.SetItemText(nCurrentItem, 2, contact.GetAddress());
    m_cList.SetItemText(nCurrentItem, 3, contact.GetPhoneNumber());

    switch(m_c_AddressBook.GetFileVersion())
    {
      case 1:
      break;
      case 2:
      m_cList.SetItemText(nCurrentItem, 4, contact.GetMobileNumber());
      m_cList.SetItemText(nCurrentItem, 5, contact.GetEmail());
      break;
    }
  }
}

Conclusions:
The MFC’s Document View architecture offers complete serialization support. Each MDI/SDI application contains default serialization support. My demo solution presented is an adapted serialization version for dialog base applications.

Download demo application: SerAddressBook (Visual C++ 2005 project)