我必须从 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 中定义它,我试过各种随机的东西,但无济于事。有任何想法吗?
因为您想要的 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)