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

Juh*_*a K 3 xml sas

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

XML 文件具有 3 级结构,单个 lvl 2 元素包含所有 lvl 3 元素。无论我尝试什么,我所有的 3 级元素似乎都会产生自己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;
Run Code Online (Sandbox Code Playgroud)

所需的输出 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>
Run Code Online (Sandbox Code Playgroud)

我创建的 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>
Run Code Online (Sandbox Code Playgroud)

使用 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;
Run Code Online (Sandbox Code Playgroud)

实际结果 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>
Run Code Online (Sandbox Code Playgroud)

重现步骤

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

问题

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

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

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

Par*_*ait 5

因为您想要的 XML 涉及一个有点复杂的分组,请考虑XSLT,这是一种旨在转换 XML 文件的专用语言。SAS 9.4 维护一个 XSLT 处理器,使用 Saxon-EE 9.3 版引擎和proc xsl,允许 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在 XSLT 内部进行硬编码,concat()并作为<COL>指定模板中的节点。对于其他列,相应地添加到这些部分。该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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

SAS

** 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;
Run Code Online (Sandbox Code Playgroud)

输出

<?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>
Run Code Online (Sandbox Code Playgroud)