Как прочитать файл XML с неопределенным пространством имен с помощью XMLReader?

Я относительно новичок в анализе XML-файлов и пытаюсь прочитать большой XML-файл с помощью XMLReader.

<?xml version="1.0" encoding="UTF-8"?>
<ShowVehicleRemarketing environment="Production" lang="en-CA" release="8.1-Lite" xsi:schemaLocation="http://www.starstandards.org/STAR /STAR/Rev4.2.4/BODs/Standalone/ShowVehicleRemarketing.xsd">
  <ApplicationArea>
    <Sender>
      <Component>Component</Component>
      <Task>Task</Task>
      <ReferenceId>w5/cron</ReferenceId>
      <CreatorNameCode>CreatorNameCode</CreatorNameCode>
      <SenderNameCode>SenderNameCode</SenderNameCode>
      <SenderURI>http://www.example.com</SenderURI>
      <Language>en-CA</Language>
      <ServiceId>ServiceId</ServiceId>
    </Sender>
    <CreationDateTime>CreationDateTime</CreationDateTime>
    <Destination>
      <DestinationNameCode>example</DestinationNameCode>
    </Destination>
  </ApplicationArea>
...

Я получаю следующую ошибку

ErrorException [Предупреждение]: XMLReader::read() [xmlreader.read]: compress.zlib://D:/WebDev/example/local/public/../upload/example.xml.gz:2: ошибка пространства имен: Префикс пространства имен xsi для schemaLocation в ShowVehicleRemarketing не определен

Я искал вокруг и не могу найти много полезной информации об использовании XMLReader для чтения XML-файлов с пространствами имен. Как мне определить пространство имен, если это действительно то, что мне нужно сделать... мало помощи? ссылки на соответствующие ресурсы?


person MeatFlavourDev    schedule 24.08.2010    source источник
comment
возможный дубликат Как читать XML файл, имеющий пространство имен с XMLReader?   -  person VolkerK    schedule 24.08.2010
comment
Хотя заголовок этого вопроса мне нравится намного больше, чем предыдущий, он все же дублируется. Прости.   -  person VolkerK    schedule 24.08.2010
comment
Это даже не просто дубликат, это тот же пользователь снова задает тот же вопрос в течение двух часов....   -  person Abel    schedule 24.08.2010
comment
вы продублировали свой вопрос через 2 часа. Пожалуйста, не делайте этого, это никому не поможет и плохо SEO (примечание: дублирование разрешено, но не задавать один и тот же вопрос повторно). См. часто задаваемые вопросы о том, как использовать этот сайт.   -  person Abel    schedule 24.08.2010


Ответы (4)


Должно быть определение пространства имен xsi. Например.

<ShowVehicleRemarketing
  environment="Production"
  lang="en-CA"
  release="8.1-Lite"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.starstandards.org/STAR/STAR/Rev4.2.4/BODs/Standalone/ShowVehicleRemarketing.xsd"
>

Обновление: вы можете написать определяемый пользователем фильтр, а затем позволить XMLReader используйте этот фильтр, например:

stream_filter_register('darn', 'DarnFilter');
$src = 'php://filter/read=darn/resource=compress.zlib://something.xml.gz';
$reader->open($src);

Содержимое, прочитанное оболочкой compress.zlib, затем «маршрутизируется» через DarnFilter, который должен найти (первое) место, куда он может вставить объявление xmlns:xsi. Но это довольно грязно, и потребуется некоторое время, чтобы сделать это правильно (например, теоретически ведро A может содержать xs, ведро B i:schem и ведро C aLocation=")


Обновление 2: вот специальный пример фильтра в php, который вставляет объявление пространства имен xsi. В основном непроверенный (работает с одним тестом, который я провел ;-) ) и недокументированный. Воспринимайте это как доказательство концепции, а не производственный код.

<?php
stream_filter_register('darn', 'DarnFilter');
$src = 'php://filter/read=darn/resource=compress.zlib://d:/test.xml.gz';

$r = new XMLReader;
$r->open($src);
while($r->read()) {
  echo '.';
}

class DarnFilter extends php_user_filter {
  protected $buffer='';
  protected $status = PSFS_FEED_ME;

  public function filter($in, $out, &$consumed, $closing)
  {
    while ( $bucket = stream_bucket_make_writeable($in) ) {
      $consumed += $bucket->datalen;
      if ( PSFS_PASS_ON == $this->status ) {
        // we're already done, just copy the content
        stream_bucket_append($out, $bucket);
      }
      else {
        $this->buffer .= $bucket->data;
        if ( $this->foo() ) {
          // first element found
          // send the current buffer          
          $bucket->data = $this->buffer;
          $bucket->datalen = strlen($bucket->data);
          stream_bucket_append($out, $bucket);
          $this->buffer = null;
          // no need for further processing
          $this->status = PSFS_PASS_ON;
        }
      }
    }
    return $this->status;
  }

  /* looks for the first (root) element in $this->buffer
  *  if it doesn't contain a xsi namespace decl inserts it
  */
  protected function foo() {
    $rc = false;
    if ( preg_match('!<([^?>\s]+)\s?([^>]*)>!', $this->buffer, $m, PREG_OFFSET_CAPTURE) ) {
      $rc = true;
      if ( false===strpos($m[2][0], 'xmlns:xsi') ) {
        echo ' inserting xsi decl ';
        $in = '<'.$m[1][0]
          . ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
          . $m[2][0] . '>';    
        $this->buffer = substr($this->buffer, 0, $m[0][1])
          . $in
          . substr($this->buffer, $m[0][1] + strlen($m[0][0]));
      }
    }
    return $rc;
  }
}

Обновление 3: А вот специальное решение, написанное на C#

XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
// prime the XMLReader with the xsi namespace
nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");

using ( XmlReader reader = XmlTextReader.Create(
  new GZipStream(new FileStream(@"\test.xml.gz", FileMode.Open, FileAccess.Read), CompressionMode.Decompress),
  new XmlReaderSettings(),
  new XmlParserContext(null, nsmgr, null, XmlSpace.None)
)) {
  while (reader.Read())
  {
    System.Console.Write('.');
  }
}
person VolkerK    schedule 24.08.2010
comment
Хорошо.. скажем, XML является удаленным, и я не могу его изменить — есть ли способ просто игнорировать тот факт, что документ выглядит искаженным, т. е. в нем отсутствует определение пространства имен? - person MeatFlavourDev; 24.08.2010
comment
Я не думаю, что php XMLReader имеет возможность игнорировать такого рода ошибки или средства внедрения объявления пространства имен. Похоже, вам придется изменять документы, может быть, на лету, но это точно не повысит производительность. Является ли PHP вашим единственным вариантом? Например. XMLReader dotnet можно инициализировать с XmlParserContext, который уже содержит предопределенные пространства имен. см. msdn.microsoft.com/en-us/library/xc8bact5.aspx - person VolkerK; 24.08.2010
comment
PHP - единственный вариант - как вы думаете, есть ли способ изменить документ, прежде чем я попытаюсь его прочитать, не загружая все это в память? Еще пара сложностей -- он заархивирован и ~300 Мб несжатый. Все начинает выглядеть сложным/безнадежным. - person MeatFlavourDev; 24.08.2010
comment
смотрите обновление. Похоже, что требования не в пределах приятного места php. Не стесняйтесь объяснять, почему php является единственным вариантом (и также не стесняйтесь отказываться ;-)) - person VolkerK; 24.08.2010
comment
@Volker Я тоже предложил Stream Wrapper в своих комментариях. Может ли str_replace также указать в нем объявление пространства имен. - person Gordon; 24.08.2010
comment
@Gordon: Тем не менее, это уродливо, и я лишь предварительно предлагаю это решение. - person VolkerK; 24.08.2010
comment
@Volker Я нахожу StreamWrappers интригующими. Я никогда не вникал в них слишком глубоко, но идея иметь прозрачный прокси не кажется мне слишком уродливой. Я имею в виду, что str_replace определенно есть, но StreamWrapper? - person Gordon; 24.08.2010
comment
@Gordon: Согласно сообщению об ошибке, уже задействована оболочка (compress.zlib). т.е. если вы хотите написать для этого обертку, вам также придется обрабатывать сжатие в ней. Вероятно, было бы более целесообразно (и порт-/настраиваемый) написать фильтр. В любом случае, вам нужно найти место, где можно разместить дополнительный атрибут надежным и гибким способом. И тогда у вас будет еще один вызов определяемого пользователем метода для каждого фрагмента данных (+ дополнительная обработка, пока вы не вставите атрибут), что еще больше замедлит обработку 300 МБ данных xml. Дай мне попробовать... ;-) - person VolkerK; 24.08.2010

Вы можете file_get_contents и str_replace XML перед передачей его XMLReader.

Либо вставьте требуемое объявление пространства имен для префикса xsi:

$reader = new XMLReader;
$reader->xml(str_replace(
    '<ShowVehicleRemarketing',
    '<ShowVehicleRemarketing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
    file_get_contents('http://example.com/data.xml')));

Другой вариант — удалить атрибут schemaLocation:

$reader->xml(str_replace(
    'xsi:schemaLocation="http://www.starstandards.org/STAR /STAR/Rev4.2.4/BODs/Standalone/ShowVehicleRemarketing.xsd"',
    '',
    file_get_contents('http://example.com/data.xml')));

Однако, если префиксов в документе больше, вам придется заменить их все.

person Gordon    schedule 24.08.2010
comment
вздох Это сработало бы нормально, если бы размер файла не составлял ~300 МБ. Возможно, мне следует изучить какой-нибудь вариант, чтобы попытаться переписать ‹ShowVehicleRemarketing›, не загружая весь файл в память? - person MeatFlavourDev; 24.08.2010
comment
@ Феликс, хм, я никогда этого не пробовал, но вы могли бы использовать функции libxml для регистрации пользовательского фильтра потока, который изменяет данные перед их обработкой XmlReader. - person Gordon; 24.08.2010

Либо исправьте то, что записывает искаженный XML, либо напишите отдельный инструмент для исправления позже. (Не обязательно читать все это в память одновременно, обязательно - поток данных в/из, возможно, чтение и запись строки за раз.)

Таким образом, вашему читающему коду не нужно беспокоиться о попытках сделать что-то полезное с данными, и в то же время исправлять их.

person Jon Skeet    schedule 24.08.2010

Пространство имен xsi обычно зарезервировано для использования с пространством имен экземпляра схемы:

xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'

если это не так, ваш XML-файл не совместим с XML+NS и не может быть проанализирован. Поэтому вы должны решить это в исходном документе.

Примечание о xsi: это даже более важно, чем некоторые другие возможные пространства имен, потому что оно направляет проверяющий синтаксический анализатор в правильные местоположения схемы для схемы вашего XML.

person Abel    schedule 24.08.2010