使用 PHP 将 XML 电子表格工作簿解析为 JSON


Parsing an XML Spreadsheet workbook into JSON with PHP

我,嗯,似乎迷路了。

我相信我的问题是正确解析 PHP DOMDocument 类。

我有一个来自Excel的XML电子表格,其中包含不同列的标题。(它还具有多个工作表,以帮助最终用户组织数据。

我的最终目标是使用JavaScript在地图上做标记。

下面是 XML 文件的简化示例:注意:有些数据是字符串,有些是数字,有些是HTML。

<?xml version="1.0"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook>
 <Worksheet ss:Name="data">
  <Table>
   <Row>
    <Cell><Data ss:Type="String">lat</Data></Cell>
    <Cell><Data ss:Type="String">lng</Data></Cell>
    <Cell><Data ss:Type="String">boolean_1</Data></Cell>
    <Cell><Data ss:Type="String">boolean_2</Data></Cell>
    <Cell><Data ss:Type="String">Source_documents</Data></Cell>
    <Cell><Data ss:Type="String">description</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="Number">35.032139998</Data></Cell>
    <Cell><Data ss:Type="Number">-117.346952</Data></Cell>
    <Cell><Data ss:Type="Number">1</Data></Cell>
    <Cell><Data ss:Type="Number">0</Data></Cell>
    <Cell><ss:Data ss:Type="String" xmlns="http://www.w3.org/TR/REC-html40"><Font html:Color="#000000">Copy here inside HTML </Font><I><Font html:Color="#000000">with more copy</Font></I></ss:Data></Cell>
    <Cell><Data ss:Type="String">Copy here without HTML</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="Number">43.444</Data></Cell>
    <Cell><Data ss:Type="Number">-112.005</Data></Cell>
    <Cell><Data ss:Type="Number">1</Data></Cell>
    <Cell><Data ss:Type="Number">1</Data></Cell>
    <Cell><Data ss:Type="String">Diff Marker Src</Data></Cell>
    <Cell><Data ss:Type="String">Diff Marker Desc</Data></Cell>
   </Row>
  </Table>
 </Worksheet>
 <Worksheet ss:Name="tags">
  <Table>
   <Row>
    <Cell><Data ss:Type="String">tag_label</Data></Cell>
    <Cell><Data ss:Type="String">tag_category</Data></Cell>
    <Cell><Data ss:Type="String">tag_description</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="String">boolean_1</Data></Cell>
    <Cell><Data ss:Type="String">tag_cat_A</Data></Cell>
    <Cell><Data ss:Type="String">bool_1 desc</Data></Cell>
   </Row>
   <Row>
    <Cell><Data ss:Type="String">boolean_2</Data></Cell>
    <Cell><Data ss:Type="String">tag_cat_B</Data></Cell>
    <Cell><Data ss:Type="String">bool_2 desc</Data></Cell>
   </Row>
  </Table>
 </Worksheet>
</Workbook>

我一直假设我需要将电子表格转换为 JSON 数组或结构更好的 XML 文档,我可以解析该文档以创建地图的标记。(JSON似乎更可取,以减少传输的数据)

如果这个假设是正确的,我希望有一个看起来像这样的结构:

array => {
  data => {
    [0] => {
        lat => '35.032139998',
        lng => '-117.346952',
        booleans => {
            boolean_1 => true
        },
        Source_documents => '<Font html:Color="#000000">Copy here inside HTML </Font><I><Font html:Color="#000000">with more copy</Font></I>',
        'description' => 'Copy here without HTML'
    },
    [1] => {
        lat => '43.444',
        lng => '-112.005',
        booleans => {
            boolean_1 => true,
            boolean_2 => true
        },
        Source_documents => 'Diff Marker Src',
        'description' => 'Diff Marker Desc'
    }
  },
  tags = {
    'boolean_1' => {
        tag_category => 'tag_cat_A',
        'tag_description' => 'bool_1 desc'
    },
    'boolean_2' => {
        tag_category => 'tag_cat_B',
        'tag_description' => 'bool_2 desc'
    }
  }
}

我正在使用PHP,并尝试使用DOMDocument类将XML转换为JSON。 SimpleXML对我来说工作得很好,直到加载了一个新的Excel文档,其中包括偶尔的HTML。

到目前为止,我有这个PHP代码:

function get_worksheet_table($file, $worksheet_name) {
  $dom = new DOMDocument;
  $dom->load($file);
  // returns a new instance of class DOMNodeList
  $worksheets = $dom->getElementsByTagName( 'Worksheet' );
  foreach($worksheets as $worksheet) {
    // check if right sheet
    if( $worksheet->getAttribute('ss:Name') == $worksheet_name) { 
      // trying to get entire node, or childNodeList, or ... ?
      // About here I am getting lost.
      $nodes = $worksheet->getElementsByTagName('Table')->item(0); 
      $table = new DOMDocument;
      $table->preserveWhiteSpace = false;
      $table->formatOutput = true;
      $table->createElement('Table');
      /*
         ITERATE THROUGH $nodes, ADD EACH CELL NODE'S CONTENTS 
         TO $table -- UNLESS IT HAS HTML, THEN USE DOMinnerHTML(node) 
         (DOMinnerHTML function @ http://php.net/manual/en/book.dom.php#89718)
       */
      return $table;
    }
  }
  return false;
}
$data = get_worksheet_table($file, 'data');
$tags = get_worksheet_table($file, 'tags');

那里,我尝试从$data和$tags创建关联数组,然后输出一个大的 JSON 语句以传递给我的应用程序。

但这真的是一团糟,我,就像我说的,我迷路了。

问题:

  1. 这看起来我至少走在正确的轨道上吗?
  2. 如何正确访问节点?— 我似乎将所有子节点作为一个大文本值获取。
  3. 如何遍历 DOM 以在适当的情况下访问单元格的文本内容,并以字符串而不是子节点的形式访问<data>节点的任何子节点?

任何关于更好地理解如何解析 DOMDocument 类的指示将不胜感激。我一直在阅读文档,但它让我无法理解。

非常感谢您的时间。

经过更多的研究,我找到了一种方法来实现我想要的。我不会说这是最好的方法,从远处看。

但是,我能够:

  1. 解析从 Excel 生成的 XML 电子表格,按照我想要的结构;
  2. 将其输出为 JSON;和
  3. 在生成的输出中将任何文本样式保留为 HTML。

公平地说,我没有突破HTML的极限——例如,我们实际上只是弄乱了<b><i>标签。字体标签也进来了,我决定去掉它们。

如果有更干净、更优雅的方法可以做到这一点,我不会感到惊讶——我几乎是尽快从一个对象变成一个数组——我还应该注意到,就我而言,我正在处理一个相对较小的数据负载。YMMV 用于大型项目,但如果您正在阅读本文,那么我希望这会有所帮助。

下面是我从 XML 工作表表生成数据数组的函数:

/* array_from_worksheet_table()
 * Generate an array from an XML Worksheet
 * $file needs to be the full path to your file (e.g., '/Users/jeremy/www/cms/files/yourfile.xml')
 * $worksheet_name = the name of the worksheet tab
 */
function array_from_worksheet_table($file, $worksheet_name) {
  // https://stackoverflow.com/questions/7082401/avoid-domdocument-xml-warnings-in-php
  $previous_errors = libxml_use_internal_errors(true);
  $dom = new DOMDocument;
  if( !$dom->load($file) ) {
    foreach (libxml_get_errors() as $error) {
      // print_r($error);
    }
  }
  libxml_clear_errors();
  libxml_use_internal_errors($previous_errors);

  // returns a new instance of class DOMNodeList
  $worksheets = $dom->getElementsByTagName( 'Worksheet' );
  foreach($worksheets as $worksheet) {
    if( $worksheet->getAttribute('ss:Name') == $worksheet_name) {
      // When we get a DOMNodeList, if we want to access the first item, we have to
      // then use ->item(0). Important once we want to access a deeper-level DOMNodeList
      $rows = $worksheet->getElementsByTagName('Table')->item(0)->getElementsByTagName('Row');
      $table = array();
      // Get our headings.
      // This assumes that the first row HAS our headings!
      $headings = $rows->item(0)->getElementsByTagName('Cell');
      // loop through table rows. Setting $i=1 instead of 0 means we skip the first row
      for( $i = 1; $i < $rows->length; $i++ ) {
        // this is our row of data
        $cells = $rows->item($i)->getElementsByTagName('Cell'); 
        // loop through each cell
        for( $c = 0; $c < $cells->length; $c++ ) {
          // check for data element in cell
          $celldata = $cells->item($c)->getElementsByTagName('Data');
          // If the cell has data, proceed
          if( $celldata->length ) {
            // Get HTML content of any strings
            if( $celldata->item(0)->getAttribute('ss:Type')== 'String' ) {
              // Does not work for PHP < 5.3.6
              // If you HAVE PHP 5.3.6 then use function @ https://stackoverflow.com/questions/2087103/
              // $value = xml_to_json::DOMinnerHTML( $celldata->item(0) );
              // DOMNode::C14N canonicalizes nodes into strings
              // This workaround is required for PHP < 5.3.6
              $value = $celldata->item(0)->C14N();
              // hack. remove tags like <ss:Data foo...> and </Data>
              // Necessary because C14N leaves outer tags (saveHTML did not)
              $value = preg_replace('/<([s'/:]+)?Data([^>]+)?>/i', '', $value);
              // Remove font tags from HTML. Bleah.
              $value = preg_replace('/<'/?font([^>]+)?>/i', '', $value);
            } else {
              $value = $cells->item($c)->nodeValue;
            }
            // grab label from first row
            $label = $headings->item($c)->nodeValue;
            $table[$i][$label] = $value;
          }
        }
      }
    return $table;
    }
  }
  return false;
}

这返回了一个工作表表的数组,然后我能够进一步操作该数组。

一项任务是重新组织生成的数组,以便我的布尔值都在子数组中。首先,我使用 remove_element_by_value($data, '0') 删除了所有零值(找到该函数 @ https://stackoverflow.com/a/4466181/156645)

然后我将数组键与tags数组中的值进行比较,并将它们附加到每个子数组中,如下所示($long_codes是我的标签值的简单数组):

if($data_array) {
  foreach($data_array as $key => $array) {
    foreach($array as $k => $val) {
      if( in_array($k, $long_codes)) {
        $data_array[$key]['Classify'][] = $k;
        unset($data_array[$key][$k]);
      }
    }
  }
}

输出刚好echo json_encode($the_big_array),而大阵列正好array('data' => $data_array, 'tags' => $tags_array)

希望对别人有帮助!