2013年8月2日 星期五

gSOAP -- Empty Element Tag 的產生規則整理

若一個 xml elemnt 為空,一般有兩種表示方式。
<tag></tag>
<tag />

那麼當使用 gSoap 建議的 onvif schema 產生程式碼後,如何確認哪些函數會生成 <tag />的形式呢?
gSOAP 的 stdsoap2.c中會透過soap_element_start_end_out()函數產生 "/>" 的標籤,而透過 onvif schema 所產生的程式碼,主要會透過呼叫 stdsoap2.c其中的三個函數 soap_element_href(), soap_element_null(), soap_element_nil()來產生 "/>" 的標籤。 
由於 gSoap 會自動根據 wsdl 產生 header file, 接著自動產生程式碼。
因此若我們想要強制其產生</>,則不應該手動呼叫上述的三個函數,
而應該了解其生成原理。藉著修改wsdl, 或是 header file, 來達成此功能。


以下針對 gSOAP 產生 empty element tag "/>" 的規則作一整理

一、若某個 struct 內只有一個 reuqired element, 則可以產生 "/>" 以下試舉兩例:
例1: 可以產生 <tt:Message/>
 1    struct _wsnt__NotificationMessageHolderType_Message
 2    {
 3       struct _tt__Message*             tt__Message_          
 4       1;  
 5    } Message                        1; ///< Required element.
則對應產生的程式碼便會產生 "/>"
 1    SOAP_FMAC3 int SOAP_FMAC4 
 2    soap_out__wsnt__NotificationMessageHolderType_Message(
 3    struct soap *soap, const char *tag, int id, const struct 
 4    _wsnt__NotificationMessageHolderType_Message *a, const 
 5    char *type)
 6    {
 7       (void)soap; (void)tag; (void)id; (void)type;
 8       if (soap_element_begin_out(soap, tag, soap_embedded_id(
 9       soap, id, a, 
10       SOAP_TYPE__wsnt__NotificationMessageHolderType_Message)
11       , type))
12          return soap->error;
13       if (a->tt__Message_)
14       { if (soap_out_PointerTo_tt__Message(soap, "tt:
15       Message", -1, &a->tt__Message_, ""))
16             return soap->error;
17       }
18       else if (soap_element_nil(soap, "tt:Message"))
19          return soap->error;
20       return soap_element_end_out(soap, tag);
21    }
例2:
 1    struct _trt__RemoveVideoEncoderConfiguration
 2    {
 3       tt__ReferenceToken                   ProfileToken      
 4       1; ///< Required element.
 5    };    
則對應產生的程式碼便會產生 "/>"
 1    SOAP_FMAC3 int SOAP_FMAC4 
 2    soap_out__trt__RemoveVideoEncoderConfiguration(struct 
 3    soap *soap, const char *tag, int id, const struct 
 4    _trt__RemoveVideoEncoderConfiguration *a, const char 
 5    *type)
 6    {
 7       (void)soap; (void)tag; (void)id; (void)type;
 8       if (soap_element_begin_out(soap, tag, soap_embedded_id(
 9       soap, id, a, 
10       SOAP_TYPE__trt__RemoveVideoEncoderConfiguration), type)
11       )
12          return soap->error;
13       if (a->ProfileToken)
14       { if (soap_out_tt__ReferenceToken(soap, "trt:
15       ProfileToken", -1, &a->ProfileToken, ""))
16             return soap->error;
17       }
18       else if (soap_element_nil(soap, "trt:ProfileToken"))
19          return soap->error;
20       return soap_element_end_out(soap, tag);
21    }
 
二. 若 struct 內有超過一個 required element ,則仍能可以產生 <tt:Message/>
 1    struct _trt__AddVideoSourceConfiguration
 2    {
 3        tt__ReferenceToken                   ProfileToken     
 4        1; ///< Required element.
 5        tt__ReferenceToken                   
 6        ConfigurationToken             1; ///< Required 
 7        element.
 8    };
   對應產生的程式碼如下:
 1    SOAP_FMAC3 int SOAP_FMAC4 
 2    soap_out__trt__AddVideoSourceConfiguration(struct soap 
 3    *soap, const char *tag, int id, const struct 
 4    _trt__AddVideoSourceConfiguration *a, const char *type)
 5    {
 6       (void)soap; (void)tag; (void)id; (void)type;
 7       if (soap_element_begin_out(soap, tag, soap_embedded_id(
 8       soap, id, a, 
 9       SOAP_TYPE__trt__AddVideoSourceConfiguration), type))
10          return soap->error;
11       if (a->ProfileToken)
12       { if (soap_out_tt__ReferenceToken(soap, "trt:
13       ProfileToken", -1, &a->ProfileToken, ""))
14             return soap->error;
15       }
16       else if (soap_element_nil(soap, "trt:ProfileToken"))
17          return soap->error;
18       if (a->ConfigurationToken)
19       { if (soap_out_tt__ReferenceToken(soap, "trt:
20       ConfigurationToken", -1, &a->ConfigurationToken, ""))
21             return soap->error;
22       }
23       else if (soap_element_nil(soap, "trt:
24       ConfigurationToken"))
25          return soap->error;
26       return soap_element_end_out(soap, tag);
27    }  
 
三. 若 struct 內對應的 element 是可以0~多個,並且有對應的 size 資料結構,則無法產生 <tt:Message/>
 1     struct _wsnt__SystemMessageHolderType_Message
 2     {
 3         $int                             __sizett__Message_  
 4         0;
 5         struct _tt__Message*             tt__Message_        
 6         0;  
 7     }                                  Properties            
 8     1; ///< Required element.
即使全部改成 reuqired element, 還是不能
 1     struct _wsnt__SystemMessageHolderType_Message    
 2     {
 3         $int                             __sizett__Message_  
 4         1;
 5         struct _tt__Message*             tt__Message_        
 6         1;  
 7     }Properties                        1; ///< Required 
 8     element.
對應產生的程式碼如下:
 1    SOAP_FMAC3 int SOAP_FMAC4 
 2    soap_out__wsnt__SystemMessageHolderType_Message(struct 
 3    soap *soap, const char *tag, int id, const struct 
 4    _wsnt__SystemMessageHolderType_Message *a, const char 
 5    *type)
 6    {
 7       (void)soap; (void)tag; (void)id; (void)type;
 8       if (soap_element_begin_out(soap, tag, soap_embedded_id(
 9       soap, id, a, 
10       SOAP_TYPE__wsnt__SystemMessageHolderType_Message), 
11       type))
12       return soap->error;
13       if (a->tt__Message_)
14       { int i;
15          for (i = 0; i < a->__sizett__Message_; i++)
16             if (soap_out__tt__Message(soap, "tt:Message", -1,
17             a->tt__Message_ + i, ""))
18                return soap->error;
19       }
20       return soap_element_end_out(soap, tag);
21    }
四、若同時有 Required element 以及 1~多個的 element 存在,則 Required element 仍可以正常生成 "/>"。
 1    struct _tan__ModifyRules
 2    {
 3       /// @brief Reference to an existing 
 4       VideoAnalyticsConfiguration.
 5       /// Element ConfigurationToken of type "http://www.
 6       onvif.org/ver10/schema":ReferenceToken.
 7           tt__ReferenceToken                   
 8           ConfigurationToken             1; ///< Required 
 9           element.
10       /// Size of array of struct tt__Config* is 1..
11       unbounded
12          $int                                  __sizeRule    
13          1;
14       /// Array struct tt__Config* of length 1..unbounded
15           struct tt__Config*                   Rule          
16           1;
17    };
對應產生的程式碼如下:
 1    SOAP_FMAC3 int SOAP_FMAC4 soap_out__tan__ModifyRules(
 2    struct soap *soap, const char *tag, int id, const struct 
 3    _tan__ModifyRules *a, const char *type)
 4    {
 5       (void)soap; (void)tag; (void)id; (void)type;
 6       if (soap_element_begin_out(soap, tag, soap_embedded_id(
 7       soap, id, a, SOAP_TYPE__tan__ModifyRules), type))
 8          return soap->error;
 9       if (a->ConfigurationToken)
10       { if (soap_out_tt__ReferenceToken(soap, "tan:
11       ConfigurationToken", -1, &a->ConfigurationToken, ""))
12             return soap->error;
13       }
14       else if (soap_element_nil(soap, "tan:
15       ConfigurationToken"))
16          return soap->error;
17       if (a->Rule)
18       { int i;
19          for (i = 0; i < a->__sizeRule; i++)
20             if (soap_out_tt__Config(soap, "tan:Rule", -1, a->
21             Rule + i, ""))
22                return soap->error;
23       }
24       return soap_element_end_out(soap, tag);
25    }


 
五、結論:只要 gSoap產生的 header file 內註明是 Required element,並且此 require element 只會有一個的情況。便可以正常生成 "/>"。但是如果我想要多個 element 都會生成 "/>",例如讓下列例子中的SimpleItem可以變成 <SimpleItem/>,是否有可能呢??或者該問,這樣做是否仍符合原先的 WSDL 所定義的規範??
 
   以 SimpleItem 為例,其產生的 XML 舉例如下:
 1    <tt:SimpleItem Name="Source" Value="11"></tt:SimpleItem>
 2    <tt:SimpleItem Name="State" Value="22"></tt:SimpleItem>
我們試圖要讓其變成
 1    <tt:SimpleItem Name="Source" Value="11"/>
 2    <tt:SimpleItem Name="State" Value="22"/>
其在 mime.h 內定義為
 1    struct tt__ItemList
 2    {
 3       $int                                  __sizeSimpleItem 
 4       0;
 5       struct _tt__ItemList_SimpleItem
 6       {
 7          /// @brief Item name.
 8          /// Attribute Name of type xs:string.
 9          @char*                                Name          
10          1; ///< Required attribute.
11 
12          /// @brief Item value. The type is defined in the 
13          corresponding description.
14          /// Attribute Value of type xs:anySimpleType.
15          @xsd__anySimpleType                   Value         
16          1; ///< Required attribute.
17 
18       }                                   *SimpleItem        
19       0;
20 
21       /// @brief Complex value structure.
22       /// Size of ElementItem array is 0..unbounded
23       $int                                  
24       __sizeElementItem              0;
25       struct _tt__ItemList_ElementItem
26       {
27       @char*                                Name             
28       1; ///< Required attribute.
29 
30       }                                   *ElementItem       
31       0;
32 
33       struct tt__ItemListExtension*        Extension         
34       0; ///< Optional element.
35 
36    };
對應產生的程式碼如下:
 1    SOAP_FMAC3 int SOAP_FMAC4 soap_out_tt__ItemList(struct 
 2    soap *soap, const char *tag, int id, const struct 
 3    tt__ItemList *a, const char *type)
 4    {
 5       (void)soap; (void)tag; (void)id; (void)type;
 6       if (soap_element_begin_out(soap, tag, soap_embedded_id(
 7       soap, id, a, SOAP_TYPE_tt__ItemList), type))
 8          return soap->error;
 9       if (a->SimpleItem)
10       { int i;
11          for (i = 0; i < a->__sizeSimpleItem; i++)
12             if (soap_out__tt__ItemList_SimpleItem(soap, "tt:
13             SimpleItem", -1, a->SimpleItem + i, ""))
14                return soap->error;
15       }
16       if (a->ElementItem)
17       { int i;
18          for (i = 0; i < a->__sizeElementItem; i++)
19             if (soap_out__tt__ItemList_ElementItem(soap, "tt:
20             ElementItem", -1, a->ElementItem + i, ""))
21                return soap->error;
22       }
23       if (soap_out_PointerTott__ItemListExtension(soap, "tt:
24       Extension", -1, &a->Extension, ""))
25          return soap->error;
26       return soap_element_end_out(soap, tag);
27    }   
   測試如下:
   1. 將 SimpleItem 改為 reuqire element,改成以下定義,仍然不行。
 1       $int                                  __sizeSimpleItem 
 2       0;
 3       struct _tt__ItemList_SimpleItem
 4       {
 5          @xsd__anySimpleType                   Value         
 6          0; ///< Required attribute.
 7          @char*                                Name          
 8          0; ///< Required attribute.
 9       }                                   *SimpleItem        
10       1;
   2. 移除 __sizeSimpleItem,則可以正確產生。所以問題在於 gSoap 的 code generate rule。

   3. 假設我們手動更改程式如下: 仍然有問題。
 1    for (i = 0; i < a->__sizeSimpleItem; i++)
 2    {
 3       if (a->SimpleItem + i) 
 4       { 
 5          if (soap_out__tt__ItemList_SimpleItem(soap, "tt:
 6          SimpleItem", -1, a->SimpleItem + i, ""))
 7             return soap->error;  
 8       }
 9       else if (soap_element_nil(soap, "tt:SimpleItem"))
10          return soap->error;               
11    }
此改法可能會產生類似下列的結果,這也不是我想要的。
 1    <tt:SimpleItemDescription/>
 2    <tt:SimpleItemDescription/>
   4. 因為 _tt__ItemList_SimpleItem 定義內只有 attribute 並不存在 element,所以其實可以將其看成總是不會有 element, 每次呼叫 soap_out__tt__ItemList_SimpleItem() 輸出時,都自動生成 "/>" ,可參考以下程式
 1       SOAP_FMAC3 int SOAP_FMAC4 
 2       soap_out__tt__ItemList_SimpleItem(struct soap *soap, 
 3       const char *tag, int id, const struct 
 4       _tt__ItemList_SimpleItem *a, const char *type)
 5       {
 6          if (a->Value)
 7             soap_set_attr(soap, "Value", a->Value, 1);
 8          if (a->Name)
 9             soap_set_attr(soap, "Name", a->Name, 1);
10          (void)soap; (void)tag; (void)id; (void)type;
11          if (soap_element_begin_out(soap, tag, 
12          soap_embedded_id(soap, id, a, 
13          SOAP_TYPE__tt__ItemList_SimpleItem), type))
14             return soap->error;
15          return soap_element_end_out(soap, tag);
16       }      
現在程式碼針對此情況會利用 soap_element_begin_out(), 若此時直接改成呼叫 soap_element_nil(),應該有機會達成我所想要的功能。

   5. 程式碼若更改如下,可以達到預期功能。
 1 SOAP_FMAC3 int SOAP_FMAC4 soap_out__tt__ItemList_SimpleItem(
 2 struct soap *soap, const char *tag, int id, const struct 
 3 _tt__ItemList_SimpleItem *a, const char *type)
 4 {
 5  if (a->Value)
 6   soap_set_attr(soap, "Value", a->Value, 1);
 7  if (a->Name)
 8   soap_set_attr(soap, "Name", a->Name, 1);
 9  (void)soap; (void)tag; (void)id; (void)type;
10 #if 1  // 20130805 albert.liao modified for force short 
11 empty element tag
12    if (soap_element_nil(soap, "tt:SimpleItem"))   
13       return soap->error;      
14 #else   
15  if (soap_element_begin_out(soap, tag, soap_embedded_id(soap,
16  id, a, SOAP_TYPE__tt__ItemList_SimpleItem), type))
17   return soap->error;
18  return soap_element_end_out(soap, tag);
19 #endif   
20 }
產生的 xml 如下:
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <wsnt:Notify>
 3 <wsnt:NotificationMessage>
 4 <wsnt:Message>
 5 <tt:Message UtcTime="1970-01-01T00:00:00Z">
 6 <tt:Source><tt:SimpleItem Name="Name" Value="Value"/></tt:Source>
 7 </tt:Message>
 8 </wsnt:Message>
 9 </wsnt:NotificationMessage>
10 </wsnt:Notify>

六、經過程式分析之後,我認為應該是在 wsdl2h 執行時,需要指定某種 flag,讓gSOAP知道此 Tag 只有定義 property,並沒有定義 element,因此在呼叫 soap_element_begin_out() 或是 soap_element_end_out() 時便可以直接套用 empty element tag。但目前並沒有發現此種 flag 可供操作。

 
參考資料:

  1. XML 定義