0

タイトルが示すように、複数の属性を持つ可能性がある (またはまったく属性を持たない) XML タグの解析について質問があり、これを実現する方法についての提案を探しています。しかし、最初に、少し背景が整っていると思います。

Program Oと呼ばれる PHP ベースのAIMLインタープリター スクリプトに取り組んでおり、コードを文字列置換関数 (str_replace、preg_replace など) から PHP の組み込み SimpleXML 関数を使用するように移行中です。これまでに、さまざまな AIML タグ用に作成したほぼすべての解析関数が完成し、非常にうまく機能していますが、特に 1 つのタグがシート ウォーマーを蹴っています。それが CONDITION タグです。

AIML タグ リファレンスによると、タグには 3 つの別個の「形式」があります。「複数条件」と呼ばれる NAME 属性と (VALUE|CONTAINS|EXISTS) 属性の両方を持つものと、「単一条件」と呼ばれる NAME 属性のみを持つものです。 name list-condition」、および「list-condition」と呼ばれる最後の「フォーム」は、属性をまったく持たない単純な CONDITION タグです。以前にリンクした AIML タグ リファレンスには、3 つの形式すべての例がありますが、その間に多くの単語が含まれているため、周囲の AIML コードとの関連でここでそれらを繰り返します。

FORM: マルチコンディションタグ:

<category>
  <pattern>I AM BLOND</pattern>
  <template>You sound very
    <condition name="gender" value="female"> attractive.</condition>
    <condition name="gender" value="male"> handsome.</condition>
  </template>
</category>

FORM: リスト条件タグ:

<category>
  <pattern>I AM BLOND</pattern>
  <template>You sound very
    <condition>
      <li name="gender" value="female"> attractive.</li>
      <li name="gender" value="male"> handsome.</li>
    </condition>
  </template>
</category>

FORM: 単一名リスト条件タグ

<category>
  <pattern>I AM BLOND</pattern>
  <template>You sound very
    <condition name="gender">
      <li value="female"> attractive.</li>
      <li value="male"> handsome.</li>
    </condition>
  </template>
</category> 

私が取り組んでいる以前のバージョンのスクリプトでは、CONDITION タグの「リスト条件」形式のみが使用されていました。これは最も一般的に使用される形式ですが、排他的に使用されるわけではないため、次のことができるようにする必要があります。他の 2 つの形式にも対応します。だから私の質問は:

これを効率的に達成するにはどうすればよいでしょうか。

CONDITION タグのリスト条件形式を解析する作業コードは既にあります。予備テストは、エラーをスローせず、目的の応答を生成するように見えるという点で有望に見えます (ただし、リスト条件形式の場合のみです。他の 2明らかな理由により、フォームはエラーで失敗します)。関数は次のとおりです。

function parse_condition_tag($convoArr, $element, $parentName, $level)
{
  runDebug(__FILE__, __FUNCTION__, __LINE__, 'Starting function and setting timestamp.', 2);
  $response = array();
  $attrName = $element['name'];
  if (!empty ($attrName))
  {
    $attrName = ($attrName == '*') ? $convoArr['star'][1] : $attrName;
    $search = $convoArr['client_properties'][$attrName];
    $path = ($search != 'undefined') ? "//li[@value=\"$search\"]" : '//li[not@*]';
    $choice = $element->xpath($path);
    $children = $choice[0]->children();
    if (!empty ($children))
    {
      $response = parseTemplateRecursive($convoArr, $children, $level + 1);
    }
    else
    {
      $response[] = (string) $choice[0];
    }
    $response_string = implode_recursive(' ', $response, __FILE__, __FUNCTION__, __LINE__);
    runDebug(__FILE__, __FUNCTION__, __LINE__, "Returning '$response_string' and exiting function.", 4);
    return $response_string;
  }
  trigger_error('Parsing of the CONDITION tag failed! XML = ' . $element->asXML());
}

私はまだ SimpleXML 関数を使用するのに比較的慣れていないので、明らかな何かを見落としている可能性があります。実際、まさにそうであることを願っています。:)

編集:以下の私のコメントの1つで約束されているように、私が最終的に終わった機能を追加します:

  /*
   * function parse_condition_tag
   * Acts as a de-facto if/else structure, selecting a specific output, based on certain criteria
   * @param [array] $convoArr    - The conversation array (a container for a number of necessary variables)
   * @param [object] $element    - The current XML element being parsed
   * @param [string] $parentName - The parent tag (if applicable)
   * @param [int] $level         - The current recursion level
   * @return [string] $response_string
   */

 function parse_condition_tag($convoArr, $element, $parentName, $level)
 {
   runDebug(__FILE__, __FUNCTION__, __LINE__, 'Starting function and setting timestamp.', 2);
   global $error_response;
   $response = array();
   $attrName = $element['name'];
   $attributes = (array)$element->attributes();
   $attributesArray = (isset($attributes['@attributes'])) ? $attributes['@attributes'] : array();
   runDebug(__FILE__, __FUNCTION__, __LINE__, 'Element attributes:' . print_r($attributesArray, true), 1);
   $attribute_count = count($attributesArray);
   runDebug(__FILE__, __FUNCTION__, __LINE__, "Element attribute count = $attribute_count", 1);
   if ($attribute_count == 0) // Bare condition tag
   {
     runDebug(__FILE__, __FUNCTION__, __LINE__, 'Parsing a CONDITION tag with no attributes. XML = ' . $element->asXML(), 2);
     $liNamePath = 'li[@name]';
     $condition_xPath = '';
     $exclude = array();
     $choices = $element->xpath($liNamePath);
     foreach ($choices as $choice)
     {
       $choice_name = (string)$choice['name'];
       if (in_array($choice_name, $exclude)) continue;
       $exclude[] = $choice_name;
       runDebug(__FILE__, __FUNCTION__, __LINE__, 'Client properties = ' . print_r($convoArr['client_properties'], true), 2);
       $choice_value = get_client_property($convoArr, $choice_name);
       $condition_xPath .= "li[@name=\"$choice_name\"][@value=\"$choice_value\"]|";
     }
     $condition_xPath .= 'li[not(@*)]';
     runDebug(__FILE__, __FUNCTION__, __LINE__, "xpath search = $condition_xPath", 4);
     $pick_search = $element->xpath($condition_xPath);
     runDebug(__FILE__, __FUNCTION__, __LINE__, 'Pick array = ' . print_r($pick_search, true), 2);
     $pick_count = count($pick_search);
     runDebug(__FILE__, __FUNCTION__, __LINE__, "Pick count = $pick_count.", 2);
     $pick = $pick_search[0];
   }
   elseif (array_key_exists('value', $attributesArray) or array_key_exists('contains', $attributesArray) or array_key_exists('exists', $attributesArray)) // condition tag with either VALUE, CONTAINS or EXISTS attributes
   {
     runDebug(__FILE__, __FUNCTION__, __LINE__, 'Parsing a CONDITION tag with 2 attributes.', 2);
     $condition_name = (string)$element['name'];
     $test_value = get_client_property($convoArr, $condition_name);
     switch (true)
     {
       case (isset($element['value'])):
         $condition_value = (string)$element['value'];
         break;
       case (isset($element['value'])):
         $condition_value = (string)$element['value'];
         break;
       case (isset($element['value'])):
         $condition_value = (string)$element['value'];
         break;
       default:
         runDebug(__FILE__, __FUNCTION__, __LINE__, 'Something went wrong with parsing the CONDITION tag. Returning the error response.', 1);
         return $error_response;
     }
     $pick = ($condition_value == $test_value) ? $element : '';
   }
   elseif (array_key_exists('name', $attributesArray)) // this ~SHOULD~ just trigger if the NAME value is present, and ~NOT~ NAME and (VALUE|CONTAINS|EXISTS)
   {
     runDebug(__FILE__, __FUNCTION__, __LINE__, 'Parsing a CONDITION tag with only the NAME attribute.', 2);
     $condition_name = (string)$element['name'];
     $test_value = get_client_property($convoArr, $condition_name);
     $path = "li[@value=\"$test_value\"]|li[not(@*)]";
     runDebug(__FILE__, __FUNCTION__, __LINE__, "search string = $path", 4);
     $choice = $element->xpath($path);
     $pick = $choice[0];
     runDebug(__FILE__, __FUNCTION__, __LINE__, 'Found a match. Pick = ' . print_r($choice, true), 4);
   }
   else // nothing matches
   {
     runDebug(__FILE__, __FUNCTION__, __LINE__, 'No matches found. Returning default error response.', 1);
     return $error_response;
   }
   $children = (is_object($pick)) ? $pick->children() : null;
   if (!empty ($children))
   {
     $response = parseTemplateRecursive($convoArr, $children, $level + 1);
   }
   else
   {
     $response[] = (string) $pick;
   }
   $response_string = implode_recursive(' ', $response);
   return $response_string;
 }

これを行うためのより優れた、よりエレガントな方法があるのではないかと思いますが (実際には私の人生の話です)、上記は意図したとおりに機能します。改善のための提案は、ありがたく受け入れられ、慎重に検討されます。

4

1 に答える 1

0

私は を使用していないことに注意してSimpleXMLくださいDOMDocument. DOMDocumentとの両方DOMXPathが PHP5 以降で利用可能です。

提供されたドキュメントを解析してさまざまなスタイルの条件を取得する単純なパーサー クラスを作成しました。

class AIMLParser
{
    public function parse($data)
    {
        $internalErrors = libxml_use_internal_errors(true);

        $dom = new DOMDocument();
        $dom->loadHTML($data);
        $xpath = new DOMXPath($dom);

        $templates = array();

        foreach($xpath->query('//template') as $templateNode) {
            $template = array(
                'text' => $templateNode->firstChild->nodeValue, // note this expects the first child note to always be the textnode
                'conditions' => array(),
            );

            foreach ($templateNode->getElementsByTagName('condition') as $condition) {
                if ($condition->hasAttribute('name') && $condition->hasAttribute('value')) {
                    $template['conditions'] = $this->parseConditionsWithoutChildren($template['conditions'], $condition);
                } elseif ($condition->hasAttribute('name')) {
                    $template['conditions'] = $this->parseConditionsWithNameAttribute($template['conditions'], $condition);
                } else {
                    $template['conditions'] = $this->parseConditionsWithoutAttributes($template['conditions'], $condition);
                }
            }

            $templates[] = $template;
        }

        libxml_use_internal_errors($internalErrors);

        return $templates;
    }

    private function parseConditionsWithoutChildren(array $conditions, DOMNode $condition)
    {
        if (!array_key_exists($condition->getAttribute('name'), $conditions)) {
            $conditions[$condition->getAttribute('name')] = array();
        }

        $conditions[$condition->getAttribute('name')][$condition->getAttribute('value')] = $condition->nodeValue;

        return $conditions;
    }

    private function parseConditionsWithNameAttribute(array $conditions, DOMNode $condition)
    {
        if (!array_key_exists($condition->getAttribute('name'), $conditions)) {
            $conditions[$condition->getAttribute('name')] = array();
        }

        foreach ($condition->getElementsByTagName('li') as $listItem) {
            $conditions[$condition->getAttribute('name')][$listItem->getAttribute('value')] = $listItem->nodeValue;
        }

        return $conditions;
    }

    private function parseConditionsWithoutAttributes(array $conditions, DOMNode $condition)
    {
        foreach ($condition->getElementsByTagName('li') as $listItem) {
            if (!array_key_exists($listItem->getAttribute('name'), $conditions)) {
                $conditions[$listItem->getAttribute('name')] = array();
            }

            $conditions[$listItem->getAttribute('name')][$listItem->getAttribute('value')] = $listItem->nodeValue;
        }

        return $conditions;
    }
}

ドキュメントでtemplateノードを検索し、それらをループします。ノードごとtemplateに、条件がどのようなスタイルであるかを調べます。それに基づいて、条件の正しい解析関数を選択します。すべてのテンプレートをループした後、必要なすべての情報 (私が思うに) を含む解析済みの配列を返します。

ドキュメントを解析するには、次のようにします。

$parser = new AIMLParser();
$templates = $parser->parse($someVariableWithTheContentOfTheDocument);

デモ: http://codepad.viper-7.com/JPuBaE

于 2013-02-23T16:00:03.743 に答える