DTD

外部エンティティの解決

このエントリーをはてなブックマークに追加
最終更新日 2016-12-31

XmlLite は外部エンティティを自動的には解決しません。XmlLite で外部エンティティを解決するためには、 IXmlResolver を実装した独自のリゾルバを用意する必要があります。

次に示すのは、独自のリゾルバを実装して外部エンティティを解決する例です。 あらかじめ sample.xml というファイルをプログラムと同じフォルダに用意しておき、 XML を読み込んだ結果をメッセージボックスに表示します。

プロジェクトファイル ダウンロード

// sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sample [
  <!ENTITY header SYSTEM "local://header">
  <!ENTITY library SYSTEM "local://library">
]>
<sample>
  <requirement>
    <type>ヘッダ</type>
    <name>&header;</name>
  </requirement>
  <requirement>
    <type>ライブラリ</type>
    <name>&library;</name>
  </requirement>
</sample>
// stdafx.h
#pragma once

#include "targetver.h"

#define WIN32_LEAN_AND_MEAN
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS

#include <atlbase.h>  // CComPtrを使用するため
#include <atlstr.h>   // CStringを使用するため

#include <strsafe.h>

#include <xmllite.h>
#pragma comment(lib, "xmllite.lib")
// SampleXmlResolver.h
#pragma once

class SampleXmlResolver : public IXmlResolver
{
public:
    SampleXmlResolver() : m_ref(1){}
    virtual ~SampleXmlResolver(){}

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void ** ppvObject){
        if(ppvObject == NULL){
            return E_INVALIDARG;
        }

        if(riid == __uuidof(IUnknown) || riid == __uuidof(IXmlResolver)){
            *ppvObject = static_cast<IXmlResolver *>(this);
        }else{
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
        AddRef();
        return S_OK;
    }

    virtual ULONG STDMETHODCALLTYPE AddRef(void){
        return ++m_ref;
    }

    virtual ULONG STDMETHODCALLTYPE Release(void){
        ULONG ref = --m_ref;
        if(ref == 0){
            delete this;
        }
        return ref;
    }

    virtual HRESULT STDMETHODCALLTYPE ResolveUri(
        const WCHAR * pwszBaseUri,
        const WCHAR * pwszPublicIdentifier,
        const WCHAR * pwszSystemIdentifier,
        IUnknown ** ppResolvedInput)
    {
        HRESULT hr;

        const size_t BUFFER_MAX = 256;
        WCHAR buffer[BUFFER_MAX];

        // BOM(\xFEFF)を追加した文字列をコピー
        if(::lstrcmp(pwszSystemIdentifier, L"local://header") == 0){
            hr = ::StringCchCopy(buffer, BUFFER_MAX, L"\xFEFFXmlLite.h");
        }else if(::lstrcmp(pwszSystemIdentifier, L"local://library") == 0){
            hr = ::StringCchCopy(buffer, BUFFER_MAX, L"\xFEFFXmlLite.lib");
        }else{
            return E_NOTIMPL;
        }
        if(FAILED(hr)){
            return hr;
        }

        size_t size = 0;
        hr = ::StringCchLength(buffer, BUFFER_MAX, &size);
        if(FAILED(hr)){
            return hr;
        }

        HGLOBAL hGlobal = ::GlobalAlloc(GMEM_MOVEABLE, size * sizeof(WCHAR));
        if(hGlobal == NULL){
            return E_OUTOFMEMORY;
        }

        LPVOID pv = ::GlobalLock(hGlobal);
        if(pv == NULL){
            ::GlobalFree(hGlobal);
            return E_OUTOFMEMORY;
        }
        ::CopyMemory(pv, buffer, size * sizeof(WCHAR));

        IStream *pStream = NULL;
        hr = ::CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
        if(FAILED(hr)){
            ::GlobalFree(hGlobal);
            return hr;
        }

        hr = pStream->QueryInterface(__uuidof(IUnknown), reinterpret_cast<void**>(ppResolvedInput));
        if(FAILED(hr)){
            pStream->Release();
            return hr;
        }

        return S_OK;
    }
private:
    ULONG m_ref;
};
// SampleProject.cpp
#include "stdafx.h"
#include "SampleXmlResolver.h"

void Run()
{
    CComPtr<IXmlReader> pReader;
    if(FAILED(CreateXmlReader(__uuidof(IXmlReader), reinterpret_cast<void**>(&pReader), 0))){
        MessageBox(NULL, _T("CreateXmlReader失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
        return;
    }

    // XMLファイルパス作成
    TCHAR xml[MAX_PATH];
    GetModuleFileName(NULL, xml, sizeof(xml) / sizeof(TCHAR));
    PathRemoveFileSpec(xml);
    PathAppend(xml, _T("sample.xml"));

    // ファイルストリーム作成
    CComPtr<IStream> pStream;
    if(FAILED(SHCreateStreamOnFile(xml, STGM_READ, &pStream))){
        MessageBox(NULL, _T("SHCreateStreamOnFile失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
        return;
    }

    if(FAILED(pReader->SetInput(pStream))){
        MessageBox(NULL, _T("SetInput失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
        return;
    }

    if(FAILED(pReader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Parse))){
        MessageBox(NULL, _T("SetProperty失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
        return;
    }

    // リゾルバを設定
    CComPtr<IXmlResolver> pXMLResolver;
    pXMLResolver.Attach(new SampleXmlResolver());
    if(FAILED(pReader->SetProperty(XmlReaderProperty_XmlResolver, 
        reinterpret_cast<LONG_PTR>(pXMLResolver.p)))){
        MessageBox(NULL, _T("SetProperty失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
        return;
    }

    CString result;
    LPCWSTR pwszLocalName;
    LPCWSTR pwszValue;
    XmlNodeType nodeType;
    while(S_OK == pReader->Read(&nodeType)){
        switch(nodeType){
        case XmlNodeType_Element:
            if(FAILED(pReader->GetLocalName(&pwszLocalName, NULL))){
                MessageBox(NULL, _T("GetLocalName失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
                return;
            }
            break;
        case XmlNodeType_Text:
            if(FAILED(pReader->GetValue(&pwszValue, NULL))){
                MessageBox(NULL, _T("GetValue失敗"), _T("警告"), MB_OK | MB_ICONWARNING);
                return;
            }
            CString row;
            if(lstrcmp(pwszLocalName, _T("type")) == 0){
                row.Format(_T("種類:%s\n"), pwszValue);
            }else if(lstrcmp(pwszLocalName, _T("name")) == 0){
                row.Format(_T("名前:%s\n\n"), pwszValue);
            }
            result += row;
            break;
        }
    }

    MessageBox(NULL, result, _T("結果"), MB_OK | MB_ICONINFORMATION);
}

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    Run();

    return 0;
}

まず、リゾルバを実装するためにプロジェクトに SampleXmlResolver.h というヘッダファイルを追加し、 そこに IXmlResolver の派生クラスである SampleXmlResolver というクラスを定義します。 IXmlResolver は IUnknown から派生しているため、SampleXmlResolver クラスでは QueryInterface()、AddRef()、Release() を実装します。

さらに、SampleXmlResolver クラスでは IXmlResolver::ResolveUri() を実装します。 この関数は XmlLite が外部エンティティを解決する時に呼び出され、引数は順に、 CreateXmlReaderInputWithEncodingName() や CreateXmlReaderInputWithEncodingCodePage() の第5引数で指定することができるベースURI、 エンティティの PUBLIC 識別子、エンティティの SYSTEM 識別子、解決したエンティティを表す入力オブジェクトです。 今回の例では、SYSTEM 識別子が「local://header」の場合は「XmlLite.h」という文字列を、 SYSTEM 識別子が「local://library」の場合は「XmlLite.lib」という文字列を、メモリストリームを使用して入力オブジェクトに関連付けます。

最後に、SampleProject.cpp では SampleXmlResolver.h をインクルードし、 Run() で DTD を有効にしてリゾルバを設定します。 リゾルバを設定するためには IXmlReader::SetProperty() を呼び出して XmlReaderProperty_XmlResolver にリゾルバへのポインタを設定します。