New to drupal, please let me know if I should open this up under the rest_server project. I am not sure if the http://drupal.org/project/rest_server is obsoleted now that rest_server is a sub project of services. (also the rest_server project page show no mention of a 7.x version).

I am also having some issues updating documents so I was unable to fully test whether the patch works in the grand scheme of things. The impl seems pretty straight forward though and I run into the same issues with the application/json mime type (i.e. it might just be me).

Please let me know if this works.

Comments

iphands’s picture

I got around to making a working test using PUT and XML. So the patch does fully work.

marcingy’s picture

Priority: Major » Normal

Setting to normal as this is a feature.

marcingy’s picture

Status: Active » Needs review
marcingy’s picture

Assigned: Unassigned » ygerasimov

Yuri can you review this as I don't really feel that I have the knowledge too.

kylebrowning’s picture

Status: Needs review » Needs work

iphands, can you supply the test in your patch and re-roll?

mradcliffe’s picture

It would be nicer if the patch had some error checking with libxml_get_errors(). It should also have the same for text/xml, not just application/xml.

Another question: how does json_encode and json_decode handle XML element attributes (text)?

Here's basically what I have been using for a custom xml parser. Some things would be different in the latest version of Services 3.0, but I am still stuck using RC3 because of the session bug.

This would probably be better (code morphed from a custom xml parser I have):

  $data = RESTServer::contentFromStream($handle);

  // Convert to SimpleXML object
  try {
    $xml = new SimpleXMLElement($data);
  }
  catch (Exception $e) {
    $message = '';
    foreach (libxml_get_errors() as $error) {
      $message .= t('Line @line, Col @column: @message', array('@line' => $error->line, '@column' => $error->column, '@message' => $error->message)) . "\n\n";
    }
    // Format an xml string of the message to echo out and return empty array.
  }
  // Do some custom conversion from XML to associative array and return it.
iphands’s picture

StatusFileSize
new1.2 KB

kylebrowning, sorry. It sounds like you were expecting that my test was a PHP unit test. The test I wrote to prove the patch does work is not a unit test but rather a functional test from a client (using curl as a client and bash as the language).

Here is the test that I was using. It basically uses curl to:
- authenticate
- store cookies
- fetch the node via an http GET and expecting XML from the server
- modify the XML retrieved (change the title)
- change the node that exists by supplying the modified XML via an http PUT command

Please let me know if this helps.

cotto’s picture

iphands, what's the reason for round-tripping the data through json?

cotto’s picture

StatusFileSize
new1.74 KB

Here's a 6.x version that implements mradcliffe's suggestions without the roundtrip through json. I hate that none of the current request parsers do error checking (I'm looking at you, json) and don't see the benefit of continuing that trend. I'll need xml request parsing for work (no idea why they'd want it, but there are better battles) so once a consensus starts to form around the right fix, I can put together a 7.x version and tests.

kylebrowning’s picture

Category: feature » task

Im fin with this, lets see a 7.x version.

cotto’s picture

StatusFileSize
new1.8 KB

Here's a direct port to 7.x-3.x, though without any tests. It also might be appropriate to either conditionally enable the xml parser if libxml is present or to check for libxml in hook_requirements.

ygerasimov’s picture

Assigned: ygerasimov » Unassigned

@cotto I think conditionally enable xml parser is very good idea. Also we would need documentation update that if you would like to use XML parser please make sure libxml is available.

Regarding tests, lets have it similar way like ServicesParserTests::testJSONCall() test does.

iphands’s picture

Wow! Thanks guys, I disappear for a bit and this thing continues to make progress. In response to cotto, the only reason for round tripping through JSON is that the JSON to PHP array conversion is/was a known supported conversion (it is what was already being used in the json content-type handler). I looked for a direct XML to PHP array method, but could not find such a thing. I figured it would more bug free to convert XML to JSON via a known good method, then JSON to PHP array via a known good method than to roll something myself.

aka. I am a PHP/Drupal n00b :-)

Here is one thing that have noticed when enabling XML support (it does not really make a difference which patch I try if I recall correctly (currently I am using the patch proovided in comment #11)).

Certain data structures within Drupal do not get serialized or unserialized correctly (I am not sure which one). Please bear with me as I try to coherently express the issue.

Lets say I do a GET on /node/123 with the "Accept: application/json" header set, and in the response I notice the following:

    "field_foo_txt": {
        "und": [
            {
                "safe_value": "<p>Test foo</p>\n",
                "value": "Test foo",
                "format": "markdown"
            }
        ]
    },

In a PUT with "Content-Type: application/json" if I wish to modify the value of this field I use this in the request payload:

    "field_foo_txt": {
        "und": [
            {
                "value": "Test bar",
                "format": "markdown"
            }
        ]
    },

I would expect that using XML in the GET and PUT would behave similarly, but alas it does not.
For example, a GET "Accept: application/xml" on the same node would give a response with the following:

  <field_foo_txt>
    <und is_array="true">
      <item>
        <value>Test foo</value>
        <format>markdown</format>
        <safe_value>&lt;p&gt;Test foo&lt;/p&gt;</safe_value>
      </item>
    </und>
  </field_foo_txt>

But if I PUT "Content-Type: application/xml" with this in my payload:

  <field_foo_txt>
    <und is_array="true">
      <item>
        <value>Test bar</value>
        <format>markdown</format>
      </item>
    </und>
  </field_foo_txt>

I see no change on the node.

To debug this I have used the following patch:

$ git diff
diff --git a/sites/all/modules/custom/services/resources/node_resource.inc b/sites/all/modules/custom/services/resources/node_resource.inc
index 7cc3e0e..dd18ec1 100644
--- a/sites/all/modules/custom/services/resources/node_resource.inc
+++ b/sites/all/modules/custom/services/resources/node_resource.inc
@@ -250,6 +250,8 @@ function _node_resource_create($node) {
   $form_state['values'] = $node;
   $form_state['values']['op'] = variable_get('services_node_save_button_' . $node_type . '_resource_create', t('Save'));
 
+  watchdog("node_resource_debug", print_r($form_state, TRUE));
+
   drupal_form_submit($node_type . '_node_form', $form_state, (object)$node);
 
   if ($errors = form_get_errors()) {

What I find is that the $form_state Array in the working example (JSON) looks like this:

(
    [values] => Array
        (
            [title] => Changed from JSON Services PUT
            [type] => article
            [field_foo_txt] => Array
                (
                    [und] => Array
                        (
                            [0] => Array
                                (
                                    [value] => Test bar
                                    [format] => markdown
                                )

                        )

                )

            [name] => testuser
            [op] => Save
        )
)

But using the non working example (XML) the $form_state Array looks as follows:

(
    [values] => Array
        (
            [title] => Changed from XML Services PUT
            [type] => article
            [field_foo_txt] => SimpleXMLElement Object
                (
                    [und] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [is_array] => true
                                )

                            [item] => SimpleXMLElement Object
                                (
                                    [value] => Test bar
                                    [format] => markdown
                                )

                        )

                )
            [name] => testuser
            [op] => Save
        )

)

I hope my explanation makes sense. Can anyone offer some insight here? Also, this issue seems to only affect feilds where the value is an array.
Would this be an issue with the serialization in the first reponse? or an issue with deserialization in the resulting request?

Thanks,
-Ian Page Hands

mradcliffe’s picture

Your serialization issue has to do with the XML result formatter, not the XML request parser. It is escaping tags that may not be valid. You can customize how results are formatted to change this behavior. For instance, I wrap data that I found like this in CDATA tags (I have to format nested XML documents :*( ).

In terms of request parsing, I think everyone writes their own XML to Array parser at some point (there's even one on the PHP SimpleXML manual page). I did so that I could handle those XML attributes in a specific way per my own Rest API.

iphands’s picture

Your serialization issue has to do with the XML result formatter, not the XML request parser. It is escaping tags that may not be valid. You can customize how results are formatted to change this behavior. For instance, I wrap data that I found like this in CDATA tags (I have to format nested XML documents :*( ).

Can you elaborate here or restate this? I don't quite grok the explanation, but I think it is due to my own lack of experience with the issue at hand.
Thanks.

iphands’s picture

Your serialization issue has to do with the XML result formatter, not the XML request parser.

I wonder about this statement.

The thing is, after this code is run (RESTServer.inc->parseRequest()):

          if ($handle) {
            $data = call_user_func($parser, $handle);
            fclose($handle);
          }
        }
        return $data;

I think (but I am not sure) the $data that is returned is Content-Type agnostic.
i.e. no matter which content-type is choosen parseRequest() should return a similar data structure.
Right?

And if that is true, I can't see what XML would ever result in an array like:

            [field_foo_txt] => Array
                (
                    [und] => Array
                        (
                            [0] => Array
                                (
                                    [value] => Test bar
                                    [format] => markdown
                                )
                        )
                )

I would always get arrays like:

            [field_foo_txt] => SimpleXMLElement Object
                (
                    [und] => SimpleXMLElement Object
                        (
                            [@attributes] => Array
                                (
                                    [is_array] => true
                                )
                            [item] => SimpleXMLElement Object
                                (
                                    [value] => Test bar
                                    [format] => markdown
                                )
                        )
                )

And the second array is not acceptable.
Am I right in my assumption? or again, am I missing something.

iphands’s picture

Status: Needs work » Needs review
StatusFileSize
new1.69 KB

For what it is worth I came up with and am using this patch for servers/rest_server/includes/RESTServer.inc
The patch correctly unmarshals the XML so that Services accepts the same style/format of XML payload that Services sends down.

Please let me know if this is not the desired solution, or if there is some bug in the algorithm.
Thanks,
-Ian Page Hands

kylebrowning’s picture

Status: Needs review » Needs work

i don't mind the patch, but it needs a lot of work.

Theres watchdog statements that run regardless of if the endpoint is in the debug mode, and you have really commented anything your doing.

Does this require libxml be enabled for php/apache and if so, there needs to be a hook_requirements change if this option is enabled for any endpoint.

mradcliffe’s picture

Yes, SimpleXML itself requires libxml to be enabled. This is a default configuration option for PHP so it should be on most servers out there (including Windows). I don't think that it's necessary to have this in hook_requirements for this reason.

I'm going to take a look at this a bit more in depth and compare it to my own custom parser.

iphands’s picture

Status: Needs work » Needs review
StatusFileSize
new3.74 KB

i don't mind the patch, but it needs a lot of work.

Theres watchdog statements that run regardless of if the endpoint is in the debug mode, and you have really commented anything your doing.

D'OH that watchdog statement was left over from development / unintentional. Here is a new patch without the debug statements and commented code.

kylebrowning’s picture

Version: 7.x-3.x-dev » 6.x-3.x-dev
Status: Needs review » Needs work

6.x patch please then we will commit.

ygerasimov’s picture

Assigned: Unassigned » ygerasimov
hypertext200’s picture

Version: 6.x-3.x-dev » 7.x-3.x-dev
Status: Needs work » Needs review
StatusFileSize
new3.71 KB

This is the patch with protected on unmarshalXML since in case of altering data.

Status: Needs review » Needs work

The last submitted patch, 1232984-services-xml-parser-23.patch, failed testing.

hypertext200’s picture

Status: Needs work » Needs review
StatusFileSize
new3.71 KB

#23 with do-not-test flag.

hypertext200’s picture

#20, #23 and #25 has some issue when we have one or more elements in a field. So here is the structure of node.xml.

<?xml version="1.0" encoding="UTF-8"?>
<node>
   <title>NODE TITLE</title>
   <type>NODE TYPE</type>
   <field_text>
      <und is_array="TRUE">
         <item>
            <value>Here is the text</value>
         </item>
      </und>
   </field_text>
   <field_text_unlimited>
      <und is_array="TRUE">
         <item>
            <value>Here is the text 1</value>
         </item>
         <item>
            <value>Here is the text 2</value>
         </item>
         <item>
            <value>Here is the text 3</value>
         </item>
      </und>
   </field_text_unlimited>
</node>

Patch attached.

hypertext200’s picture

StatusFileSize
new3.16 KB

Fixed the issue with checkboxes. For the checkboxes we need to send XML as below.

   <field_CHECKBOXES>
      <und is_array="true">
         <item><value is_raw="TRUE">1</value></item>
         <item><value is_raw="TRUE">1</value></item>
      </und>
   </field_CHECKBOXES>

Status: Needs review » Needs work

The last submitted patch, 1232984-services-xml-parser-27.diff, failed testing.

kylebrowning’s picture

Status: Needs work » Needs review
kylebrowning’s picture

Status: Needs review » Closed (fixed)
klokie’s picture

Version: 7.x-3.x-dev » 6.x-3.3
Status: Closed (fixed) » Needs review
StatusFileSize
new3.22 KB

I've re-rolled the patch from #27 for 6.x-3.x.

Status: Needs review » Needs work

The last submitted patch, 1232984-services-xml-parser-31.diff, failed testing.

marcingy’s picture

Status: Needs work » Closed (won't fix)

Drupal 6 is no longer supported