(Web API)Xmlの解析を教えてください

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
結城
記事: 52
登録日時: 9年前
連絡を取る:

(Web API)Xmlの解析を教えてください

#1

投稿記事 by 結城 » 9年前

どうも、先ほどはお騒がせいたしました
こちらが本当に聞きたかったことなのです

このソフトの本当の目的はYahoo! Web APIに接続し、
返ってきたXmlを解析、データとして利用できる形に持っていくとなのですが

なんとかWinHttpで正常にXmlは取得できました……が
肝心のXmlを解析できません

いくつかWebを当たってみるもVBなどでの情報は見つかりますが
C/C++で理解できるサイトが見つかりません

また、いくつかライブラリもあるようですが
やはりわかりやすく使い方を書いてくれているサイトはどこにも見つかりません
一応、ライブラリのサイトも拝見して見ました
しかし、相変わらずMicroSoftはわかりにくく、LibXml2などは英語なのでまともに読めやしませんw

どなたかXmlの解析方法について詳しく載っているサイトを知っている
または、その方法を知っている方がいたら教えてください
お願いします

開発環境
Win XP Home SP3
Visual Studio 2005 C++
DXライブラリ 3.02e(※3.04bのUpdateLayerdWindowForSoftImageバグ修正待ち 掲示板にて)

ファイルはさっきのスレで上げたのと同じなので
リンクはっときます
http://dixq.net/forum/download/file.php?id=45
ANGE;ART

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#2

投稿記事 by softya(ソフト屋) » 9年前

私は海外製の英語でしか説明がないパーサを利用しているのでご紹介しませんが、こちらなら日本語で書かれているので試されたらどうでしょうか?
「SXML: C言語用簡易XML解析ライブラリ」
http://www.mysticwall.com/software/sxml/
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
結城
記事: 52
登録日時: 9年前
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#3

投稿記事 by 結城 » 9年前

softya(ソフト屋)さんありがとうございます
すこしサイトを参考に組んでみて
またわからないことがあったら質問しますので
その時はまたどなたかよろしくお願いします
ANGE;ART

アバター
結城
記事: 52
登録日時: 9年前
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#4

投稿記事 by 結城 » 9年前

すいません
えーと、先ほど教えていただいたサイトの中身は一通り理解したのですが
少し困った場所があります

先にあげたコードではWeb APIからテキストデータを貰っているのですが
以下のコードの
if(fd=open("ex.xml", O_RONDLY ,0400)!=-1)←ハンドル作成
{
...
if((root=sxml_parse_file(fd))!=NULL)←Xml読み込み
{
...
を見る限りテキストからの読み込みになっています
なるだけ余計な作業をしてスピードを落としたくないので
t01からそのまま受け継げるようにしたいのですが
どうすれば受け継げるでしょうか……?
標準関数(?)には疎いのでどうか教えてください

以下が問題のコードです

コード:


  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <string.h>
  #include <fcntl.h>
  #include <sxml.h>

  #define TAG_ROOT        "server"
  #define ROOT_ATTR_NAME  NULL
  #define ROOT_ATTR_VALUE NULL
  #define TAG_ADDRESS     "address"
  #define TAG_PORT        "port"

  static void parse(sxml_node_t * node);

  int main(int argc, char * argv[])
  {
    int fd;

    if ((fd = open("ex.xml", O_RDONLY, 0400)) != -1) {
      sxml_node_t * root; /* 解析木を保存する変数 */

      /* XML の解析結果が root 変数に設定される */
      if ((root = sxml_parse_file(fd)) != NULL) {
        parse(root);
        sxml_delete_node(root); /* メモリ解放 */
      }
      close(fd);
    }

    return 0;
  }

  static void
  parse(sxml_node_t * root)
  {
    sxml_node_t * node;

    /*
     * TAG_ROOT の要素名で ROOT_ATTR_NAME 属性と
     * ROOT_ATTR_VALUE 値を持つタグから解析を開始する
     */
    node = sxml_find_element(root, TAG_ROOT, ROOT_ATTR_NAME,
                                          ROOT_ATTR_VALUE);
    if (node != NULL) {
      sxml_node_t * np;

      for (np = sxml_get_child(node); np != NULL;
           np = sxml_get_next_sibling(np)) {
        const char * content;

        /* タイプが要素でない場合はスキップ */
        if (sxml_get_type(np) != SXML_ELEMENT) { continue; }

        /* 要素名が存在しない場合はスキップ */
        if (sxml_get_element_name(np) == NULL) { continue; }

        /* np 変数で示される子要素の内容を content に設定 */
        content = sxml_get_content(sxml_get_child(np));

        /* <address> タグの処理 */
        if (strcmp(sxml_get_element_name(np), TAG_ADDRESS) == 0) {
          fprintf(stdout, "address: %s\n", content);
        }
        /* <port> タグの処理 */
        else if (strcmp(sxml_get_element_name(np), TAG_PORT) == 0) {
          fprintf(stdout, "port: %s\n", content);
        }
        else {
          /* 定義されていないタグ名をここで表示 */
          fprintf(stderr, "WARN: unknown tag: %s\n",
                  sxml_get_element_name(np));
        }
      }
    }
  }

それともroot.value.contentに直接代入してしまえばいいのでしょうか…?
ANGE;ART

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#5

投稿記事 by softya(ソフト屋) » 9年前

すいません。
UNIX系以外はマズイですね。このコード。

代案として、とりあえず私の使っている「Parsifal XML Parser C library」です。
http://www.saunalahti.fi/~samiuus/toni/xmlproc/
英語しかないので、完全にご期待には添えないですが、

あとの手段としては
「SXML: C言語用簡易XML解析ライブラリ」
も複雑ではないので改造したほうが速いかも知れません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
結城
記事: 52
登録日時: 9年前
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#6

投稿記事 by 結城 » 9年前

少し読んでみましたがすぐに挫折してしまいました……

改造、ですか……わかりました、少し考えてみます

ところで、なぜかあのコードで取得した文字列をChar*に変換した後、画面にためしに書き出してみると二バイト文字が文字化けしています
同じ文字が頻出していることなどから、
どうやらエンコードが違うか、データの開始位置がずれているかのようなのですが

この原因は何なんでしょうか?
少しでもわかる方教えてください

ちなみに私の実行した結果です…文字が化けまくってます

コード:

<?xml version="1.0" encoding="UTF-8" ?>
[tab=30]<ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:jp:jlp" xsi:schemaLocation="urn:yahoo:jp:jlp http://jlp.yahooapis.jp/MAService/V1/parseResponse.xsd">
[tab=40]<ma_result>
[tab=50]<total_count>9</total_count>
[tab=50]<filtered_count>9</filtered_count>
[tab=60]<word_list>
[tab=60]<word>
[tab=70]<surface>ao-</surface>
[tab=70]<reading>a?≪a??</reading>
[tab=70]<pos>a??ec?</pos>
[tab=70]</word><word>
[tab=70]<surface>a?≪</surface>
[tab=70]<reading>a?≪</reading>
[tab=70]<pos>a?cec?</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>a? ̄</surface>
[tab=70]<reading>a? ̄</reading>
[tab=70]<pos>a?cec?</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>ao?</surface>
[tab=70]<reading>2</reading>
[tab=70]<pos>a??ec?</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>c??</surface>
[tab=70]<reading>a??</reading>
[tab=70]<pos>a?\a°?e??</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>a??a? ̄a??a?a</surface>
[tab=70]<reading>a?≪a??a?¨a??</reading>
[tab=70]<pos>a??ec?</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>a??</surface>
[tab=70]<reading>a??</reading>
[tab=70]<pos>a?cec?</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>a??a??</surface>
[tab=70]<reading>a??a??</reading>
[tab=70]<pos>a??ec?</pos>
[tab=60]</word>
[tab=60]<word>
[tab=70]<surface>a€?</surface>
[tab=70]<reading>a€?</reading>
[tab=70]<pos>c?1aR?</pos>
[tab=60]</word>
[tab=60]</word_list>
[tab=50]</ma_result>
[tab=50]<uniq_result>
[tab=60]<total_count>9</total_count>
[tab=60]<filtered_count>4</filtered_count>
[tab=70]<word_list>
[tab=70]<word>
[tab=80]<count>1</count>
[tab=80]<surface>a??a??</surface>
[tab=80]<reading/>
[tab=90]<pos>a??ec?</pos>
[tab=90]</word><word>
[tab=90]<count>1</count>
[tab=90]<surface>a??a? ̄a??a?a</surface>
[tab=80]<reading/>
[tab=80]<pos>a??ec?</pos>
[tab=70]</word>
[tab=70]<word>
[tab=80]<count>1</count>
[tab=80]<surface>ao?</surface>
[tab=80]<reading/>
[tab=90]<pos>a??ec?</pos>
[tab=80]</word>
[tab=80]<word>
[tab=80]<count>1</count>
[tab=80]<surface>ao-</surface>
[tab=80]<reading/>
[tab=80]<pos>a??ec?</pos>
[tab=60]</word>
[tab=40]</word_list>
[tab=20]</uniq_result>
</ResultSet>
※このコードを実行するにはYahoo! デベロッパーに登録し、アプリケーションIDを取得していなければいけません
https://e.developer.yahoo.co.jp/webserv ... pplication ←ここからとれるのでどうかよろしくお願いいたします

コード少し変わってます↓
添付ファイル
test002.zip
(4.53 KiB) ダウンロード数: 22 回
ANGE;ART

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#7

投稿記事 by softya(ソフト屋) » 9年前

確実に文字コード化けしてますね。
たぶん、WinHttpが犯人だと思います。
私はWinsockからしかHTTPを使ったことがないので、WinHttpの文字コード変換部分がどうなっているか良く分かりません。
もう少し調べてみますが。

XMLの件ですが、こちらも検討してみてください。
http://docs.solab.jp/xmllite/
ストリームから読み込むにあるメモリストリームで解決すると思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 9年前
住所: 東海地方
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#8

投稿記事 by softya(ソフト屋) » 9年前

「Geekなぺーじ : winsockプログラミング」
http://www.geekpage.jp/programming/winsock/
をベースに作りました。
受信したデータは、不要なHTTPヘッダが付いているで数行は読み飛ばしてXML処理してください。
あと結構雑な処理なので、「HTTPクライアントの作成(2)」などを参考に書き換えてください。

コード:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

int
main(int argc, char *argv[])
{
 WSADATA wsaData;
 struct sockaddr_in server;
 SOCKET sock;
 char buf[1024];
 char *deststr = "jlp.yahooapis.jp";
 unsigned int **addrptr;

 if (WSAStartup(MAKEWORD(2,0), &wsaData) != 0) {
	 printf("WSAStartup failed\n");
	 return 1;
 }

 sock = socket(AF_INET, SOCK_STREAM, 0);
 if (sock == INVALID_SOCKET) {
	 printf("socket : %d\n", WSAGetLastError());
	 return 1;
 }

 server.sin_family = AF_INET;
 server.sin_port = htons(80); // HTTPのポートは80番です

 server.sin_addr.S_un.S_addr = inet_addr(deststr);
 if (server.sin_addr.S_un.S_addr == 0xffffffff) {
	 struct hostent *host;

	 host = gethostbyname(deststr);
	 if (host == NULL) {
		 if (WSAGetLastError() == WSAHOST_NOT_FOUND) {
			 printf("host not found : %s\n", deststr);
		 }
		 return 1;
	 }

	 addrptr = (unsigned int **)host->h_addr_list;

	 while (*addrptr != NULL) {
		 server.sin_addr.S_un.S_addr = *(*addrptr);

		 // connect()が成功したらloopを抜けます
		 if (connect(sock,
				(struct sockaddr *)&server,
				sizeof(server)) == 0) {
			break;
		 }

		 addrptr++;
		 // connectが失敗したら次のアドレスで試します
	 }

	 // connectが全て失敗した場合
	 if (*addrptr == NULL) {
		 printf("connect : %d\n", WSAGetLastError());
		 return 1;
	 }
 } else {
	 if (connect(sock,
                     (struct sockaddr *)&server,
                     sizeof(server)) != 0) {
		 printf("connect : %d\n", WSAGetLastError());
		 return 1;
	 }
 }

 // HTTPで「/」をリクエストする文字列を生成
 memset(buf, 0, sizeof(buf));
 _snprintf(buf, sizeof(buf), "GET http://%s%s HTTP/1.0\r\n\r\n"
 							,deststr
 							,"/MAService/V1/parse?appid=アプリケーションID&results=ma,uniq&uniq_filter=9|10&sentence=%E5%BA%AD%E3%81%AB%E3%81%AF%E4%BA%8C%E7%BE%BD%E3%83%8B%E3%83%AF%E3%83%88%E3%83%AA%E3%81%8C%E3%81%84%E3%82%8B%E3%80%82"
 );

 // HTTPリクエスト送信
 int n = send(sock, buf, (int)strlen(buf), 0);
 if (n < 0) {
	 printf("send : %d\n", WSAGetLastError());
	 return 1;
 }

 // サーバからのHTTPメッセージ受信
 FILE *fp = fopen( "data.xml","wb" );
 while (n > 0) {
	 memset(buf, 0, sizeof(buf));
	 n = recv(sock, buf, sizeof(buf), 0);
	 if (n < 0) {
		 printf("recv : %d\n", WSAGetLastError());
		 return 1;
	 }

	 // 受信結果を出力
	 fwrite(buf, n, 1, fp);
 }
 fclose(fp);

 closesocket(sock);

 WSACleanup();

 return 0;
}
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: (Web API)Xmlの解析を教えてください

#9

投稿記事 by YuO » 9年前

文字コードはXML宣言で行われている (text/xmlだからXML宣言の文字コードは無視するべきでは,というは気にしない) ので,単純にResponseTextはストリームをISO-8859-1として処理しているのでしょう。
実際,Fiddlerで通信をキャプチャしてUTF-8として見ると,正しく返っている事が解ります。

ResponseStreamからストリームを取得して,それを文字コード変換してやる必要があります。

で,そんなの面倒だから,という理由でMSXMLでそのまま叩いてみました。
loadするところでResponseStream突っ込めばWinHttpの結果を使えるはずです。

コード:

#include <cstdio>
#include <cwchar>
#include <vector>
#include <string>
#include <limits>
#include <windows.h>
#import  <msxml6.dll>

#undef max

const wchar_t API_URL[] = L"http://jlp.yahooapis.jp/MAService/V1/parse?appid= {insert your yahoo-application id} &results=ma,uniq&uniq_filter=9|10&sentence=%E5%BA%AD%E3%81%AB%E3%81%AF%E4%BA%8C%E7%BE%BD%E3%83%8B%E3%83%AF%E3%83%88%E3%83%AA%E3%81%8C%E3%81%84%E3%82%8B%E3%80%82";

// コンソール出力用 (Unicode文字をAPIで出力する)
void WriteString (const wchar_t * format, ...)
{
    std::vector<wchar_t> buffer;
    for (std::size_t max_count = 256; max_count < std::numeric_limits<std::size_t>::max() - 256; max_count += 256)
    {
        buffer.resize(max_count);
        va_list vl;
        va_start(vl, format);
        std::size_t written = std::vswprintf(&buffer[0], max_count - 1, format, vl);
        va_end(vl);

        if (written >= max_count - 1)
        {
            continue;
        }
        break;
    }

    std::wstring stringToWrite(&buffer[0]);
    DWORD written;

    ::WriteConsoleW(::GetStdHandle(STD_OUTPUT_HANDLE), stringToWrite.c_str(), stringToWrite.size(), &written, 0);
    ::OutputDebugStringW(stringToWrite.c_str());
}

int main (void)
{
    // COMの初期化と後片付け
    struct ComInitializer {
        ComInitializer()
        {
            ::CoInitialize(NULL);
        }
        ~ComInitializer()
        {
            ::CoUninitialize();
        }
    } COM_INITIALIZER;

    // DOMの作成
    MSXML2::IXMLDOMDocument2Ptr xmlDom;
    HRESULT hr;
    if (FAILED(hr = xmlDom.CreateInstance(__uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER)))
    {
        WriteString(L"DOM Create Error (%08X)\n", hr);
        return 1;
    }

    try
    {
        // APIの読み込み
        xmlDom->async = VARIANT_FALSE;
        xmlDom->validateOnParse = VARIANT_FALSE;
        xmlDom->resolveExternals = VARIANT_FALSE;

        if (xmlDom->load(API_URL) != VARIANT_TRUE)
        {
            WriteString(L"Load Error : %s\n", (LPCWSTR)xmlDom->parseError->Getreason());
            return 2;
        }

        // APIの名前空間設定
        xmlDom->setProperty(L"SelectionNamespaces", L"xmlns:A='urn:yahoo:jp:jlp'");
        xmlDom->setProperty(L"SelectionLanguage", L"XPath");

        // 結果の表示
        MSXML2::IXMLDOMNodeListPtr words = xmlDom->documentElement->selectNodes(L"A:ma_result//A:word[A:surface/text()][A:reading/text()][A:pos/text()]");
        for (int i = 0; i < words->length; ++i)
        {
            MSXML2::IXMLDOMNodePtr wordElement = words->item[i];
            MSXML2::IXMLDOMTextPtr surfaceText = wordElement->selectSingleNode(L"A:surface/text()");
            MSXML2::IXMLDOMTextPtr readingText = wordElement->selectSingleNode(L"A:reading/text()");
            MSXML2::IXMLDOMTextPtr posText = wordElement->selectSingleNode(L"A:pos/text()");

            std::wstring surface = surfaceText->text;
            std::wstring reading = readingText->text;
            std::wstring pos = posText->text;

            WriteString(L"@%i : %s (%s) = %s\n", i, surface.c_str(), reading.c_str(), pos.c_str());
        }
    }
    catch (const _com_error & e)
    {
        WriteString(L"Com Exception (%08X)\n", e.Error());
        WriteString(e.ErrorMessage());

        return 3;
    }
    return 0;
}
----
2010-12-03T12:10 ソースコード中の {insert your yahoo-developer id} を {insert your yahoo-application id} に修正

アバター
結城
記事: 52
登録日時: 9年前
連絡を取る:

Re: (Web API)Xmlの解析を教えてください

#10

投稿記事 by 結城 » 9年前

selectNodesの中身をどのように指定すればいいかいまいち理解できないのですが・・・

selectNodes(L"A:ma_result//A:word[A:surface/text()][A:reading/text()][A:pos/text()]")の
A:は何かの定義でしょうか?
//の時はノードを移動する(?)ということいいんでしょうか
あと[]でくくられている理由選択するノードということでしょうか?

質問ばかりで済みませんが
これらわかる人がいたら、どうか教えてください
ANGE;ART

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: (Web API)Xmlの解析を教えてください

#11

投稿記事 by YuO » 9年前

えーっと,XMLの名前空間とかXPathまわりがわかっていないと苦労するとは思います。
ただし,これらは他のDOMを使った場合でもついてまわるので,知っておいて損はないと思います。
まぁ,selectNodesとかは実はMS拡張だったりしますが。

> A:は何かの定義でしょうか?
事前に,デフォルト名前空間 (XML中でxmlns=で指定された名前空間) のプリフィックスをAだと指定しておいたので,名前空間「urn:yahoo:jp:ilp」の要素「ma_result」を指定するために A: をつけています。
# xmlDom->setProperty(L"SelectionNamespaces", L"xmlns:A='urn:yahoo:jp:jlp'");

> //の時はノードを移動する(?)ということいいんでしょうか
//はXPath式 /descendant-or-self::node()/ の省略形で,「そのノードまたはその子孫ノード」を意味します。

> あと[]でくくられている理由選択するノードということでしょうか?
述語といって,ノードを絞り込むために使います。
  • A:ma_result
    ルート要素直下のma_resultの集合を選択する
  • A:ma_result//A:word
    ルート要素直下のma_resultの子孫であるword要素の集合を選択する
  • A:ma_result//A:word[A:surface]
    ルート要素直下のma_resultの子孫であるword要素の集合を選択し,そのうちsurface要素をもつword要素に絞り込む
  • A:ma_result//A:word[A:surface/text()]
    ルート要素直下のma_resultの子孫であるword要素の集合を選択し,そのうち「テキストノードが子にあるsurface要素」を子にもつword要素に絞り込む
  • A:ma_result//A:word[A:surface/text()][A:reading/text()][A:pos/text()]
    ルート要素直下のma_resultの子孫であるword要素の集合を選択し,そのうち「テキストノードが子にあるsurface要素」を子にもつword要素に絞り込み,さらに「テキストノードが子にあるreading要素」を子にもつword要素に絞り込み,さらに「テキストノードが子にあるpos要素」を子にもつword要素に絞り込む
XPath : http://www.w3.org/TR/xpath/ (Wikipedia (ja))

閉鎖

“C言語何でも質問掲示板” へ戻る