使用XMLMAP从SAS写入分层XML文件

问题描述 投票:3回答:1

我必须从SAS数据集中生成XML文件。 XML文件的格式是非常严格定义的,我需要完全匹配它。我正在使用SAS 9.4(注意:并坚持使用它!)并使用XMLMAP和libname xmlv2。我觉得我非常接近解决方案,但最后的障碍似乎无法通过!

XML文件具有3级结构,其中包含一个包含所有lvl 3元素的lvl 2元素。无论我尝试什么,我所有的lvl 3元素似乎都会产生自己的lvl 2元素。在导入或导出完全相同的数据时,SAS xmlv2 libname引擎似乎有所不同!下面重现的示例和步骤 - 如果可以,请帮助我!

示例数据

数据是文件列表和与这些文件相关的一些属性。这些属性对所有文件都是通用的,只有列表中的文件名不同。这会在SAS工作中创建一个测试数据集:

proc sql;

create table input_data
  (col1 char(1),
  col2 char(1),
  file char(20));

insert into input_data
  values ('1', '2', 'file1.txt')
  values ('1', '2', 'file2.txt');

quit;

期望的输出XML

请注意,所有文件名都在它们自己的FILE元素中列出,嵌套在单个FILES元素中。公共属性是主FILE_INFO元素内的元素。这是我需要能够输出的结构。

<?xml version="1.0" encoding="windows-1252" ?>

<FILE_INFO>
  <COL1>1</COL1>
  <COL2>2</COL2>
  <FILES>
    <FILE>file1.txt</FILE>
    <FILE>file2.txt</FILE>
  </FILES>
</FILE_INFO>

我创建了SAS XMLMAP

<?xml version="1.0" encoding="windows-1252"?>
<!-- ############################################################ -->
<!--  this is a map file for SAS-XML conversion                   -->
<!-- ############################################################ -->
<SXLEMAP name="file_test" version="2.1">

  <!-- ############################################################ -->
  <OUTPUT>
    <TABLEREF name="FILE_INFO"/>
  </OUTPUT>

  <NAMESPACES count="0"/>

  <!-- ############################################################ -->
  <TABLE name="FILE_INFO">
    <TABLE-PATH syntax="XPath">/FILE_INFO/FILES/FILE</TABLE-PATH>

    <COLUMN name="col1" retain="YES">
      <PATH syntax="XPath">/FILE_INFO/COL1</PATH>
      <TYPE>character</TYPE>
      <DATATYPE>string</DATATYPE>
      <LENGTH>1</LENGTH>
    </COLUMN>

    <COLUMN name="col2" retain="YES">
      <PATH syntax="XPath">/FILE_INFO/COL2</PATH>
      <TYPE>character</TYPE>
      <DATATYPE>string</DATATYPE>
      <LENGTH>1</LENGTH>
    </COLUMN>

    <COLUMN name="file">
      <PATH syntax="XPath">/FILE_INFO/FILES/FILE</PATH>
      <TYPE>character</TYPE>
      <DATATYPE>string</DATATYPE>
      <LENGTH>20</LENGTH>
    </COLUMN>

  </TABLE>
</SXLEMAP>

使用XMLMAP输出XML的SAS代码

filename out "C:\myfolder\test_out.xml";
libname out xmlv2 xmltype=xmlmap xmlmap="C:\myfolder\file_test.map";

data out.FILE_INFO;
   set work.input_data;
run;

实际结果XML

<?xml version="1.0" encoding="windows-1252" ?>

<FILE_INFO>
  <FILES>
    <FILE>file1.txt</FILE>
  </FILES>
  <COL1>1</COL1>
  <COL2>2</COL2>
  <FILES>
    <FILE>file2.txt</FILE>
  </FILES>
</FILE_INFO>

重现步骤

使用上面的代码生成测试数据集。将XMLMAP保存到file_test.map中。运行SAS代码,将生成的XML与所需结果进行比较。

问题

看看那里发生了什么?所有FILE元素都在自己的FILES元素中。无论我的数据中有多少个具有单独文件名的行,都会发生这种情况:每个文件名都有自己的FILES元素。

有趣的是,如果我使用上面所需的输出XML文件,并使用相同的XMLMAP将其反馈给SAS,则生成的SAS数据集与我的原始输入数据集完全相同!

我已经尝试在XMLMAP中摆弄RETAIN-options,我尝试将FILES定义为输入数据集中的自己的列并在XMLMAP中定义它,我尝试了各种随机的东西,但无济于事。有任何想法吗?

xml sas
1个回答
4
投票

因为您所需的XML涉及一些复杂的分组,所以请考虑XSLT,这是一种用于转换XML文件的专用语言。 SAS 9.4使用带有proc xsl的Saxon-EE 9.3版引擎维护XSLT处理器,该引擎允许使用XSLT 1.0或2.0脚本。

具体来说,将数据导出到原始xml文件(无映射)并使用XSLT 1.0的Muenchian Grouping或更简单的XSLT 2.0的xsl:for-each-group。我包括两者,因为为了便携性,XSLT 1.0被广泛用作其他语言库(Java,Python,PHP,R)中的默认规范,以防您需要在SAS之外运行或未来的读者使用早期版本。

请注意,您将看到cols在concat()内的XSLT中以及在指定模板中的<COL>节点中进行了硬编码。对于其他cols,请相应地添加到这些部分。使用normalize-space()是因为SAS在文本值之前/之后填充空格。

XSLT 1.0(另存为.xsl文件)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="colkeys" match="INPUT_DATA" use="concat(col1, col2)" />

  <xsl:template match="/TABLE">
    <FILE_INFO>
      <xsl:apply-templates select="INPUT_DATA[generate-id() =
                                       generate-id(key('colkeys', concat(col1, col2)))]"/>
    </FILE_INFO>
  </xsl:template>

  <xsl:template match="INPUT_DATA">

    <COL1><xsl:value-of select="normalize-space(col1)"/></COL1>
    <COL2><xsl:value-of select="normalize-space(col2)"/></COL2>

    <FILES>
        <xsl:for-each select="key('colkeys', concat(col1, col2))">
            <FILE><xsl:value-of select="normalize-space(file)"/></FILE>
        </xsl:for-each>
    </FILES>
  </xsl:template>

</xsl:stylesheet>

XSLT 2.0(另存为.xsl文件)

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="colkeys" match="INPUT_DATA" use="concat(col1, col2)" />

  <xsl:template match="/TABLE">
    <FILE_INFO>
      <xsl:for-each-group select="INPUT_DATA" group-by="concat(col1, col2)">          
        <COL1><xsl:value-of select="normalize-space(col1)"/></COL1>
        <COL2><xsl:value-of select="normalize-space(col2)"/></COL2>

        <FILES>
            <xsl:for-each select="current-group()">
                <FILE><xsl:value-of select="normalize-space(file)"/></FILE>
            </xsl:for-each>
        </FILES>
      </xsl:for-each-group>
    </FILE_INFO>

  </xsl:template>

</xsl:stylesheet>

** EXPORT DATASET TO XML FILE;
filename out "C:\Path\Raw_Output.xml";

libname out xml;

data out.input_data;
 set Work.input_data;
run; 

libname out clear;

proc xsl 
    in="C:\Path\Raw_Output.xml"
    out="C:\Path\Final_Output.xml"
    xsl="C:\Path\XSLT_Script.xsl";
run;

产量

<?xml version="1.0" encoding="UTF-8"?>
<FILE_INFO>
   <COL1>1</COL1>
   <COL2>2</COL2>
   <FILES>
      <FILE>file1.txt</FILE>
      <FILE>file2.txt</FILE>
   </FILES>
</FILE_INFO>
© www.soinside.com 2019 - 2024. All rights reserved.