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

使用XMLMAP从SAS写入分层XML文件,xml,sas,Xml,Sas,我必须从SAS数据集中生成一个XML文件。XML文件的格式定义非常严格,我需要精确匹配它。我使用的是SAS9.4(注意:并坚持使用!)和XMLMAPs和LibNameXMLV2。我觉得我离解决问题很近了,但还有一个我似乎无法逾越的最后障碍 XML文件有一个三级结构,一个LVL2元素包含所有LVL3元素。无论我尝试什么,我所有的3级元素似乎都会产生自己的2级元素。在导入或导出完全相同的数据时,SAS xmlv2 libname引擎的工作方式似乎有所不同!下面要复制的示例和步骤-如果可以,请帮助我

我必须从SAS数据集中生成一个XML文件。XML文件的格式定义非常严格,我需要精确匹配它。我使用的是SAS9.4(注意:并坚持使用!)和XMLMAPs和LibNameXMLV2。我觉得我离解决问题很近了,但还有一个我似乎无法逾越的最后障碍

XML文件有一个三级结构,一个LVL2元素包含所有LVL3元素。无论我尝试什么,我所有的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;
所需的输出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>
请注意,所有文件名都列在各自的文件元素中,嵌套在单个文件元素中。公共属性是主文件信息元素中的元素。这是我需要能够输出的结构

<?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>
实际结果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>

file1.txt
1.
2.
file2.txt
复制步骤

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

问题

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

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


我尝试过在XMLMAP中摆弄RETAIN选项,尝试过在输入数据集中将文件定义为它自己的列,并在XMLMAP中定义它,尝试过各种随机的东西,但都没有用。有什么想法吗?

< P>因为您所需要的XML涉及一个比较复杂的分组,请考虑设计用于转换XML文件的专用语言。SAS 9.4使用Saxon EE 9.3版引擎维护XSLT处理器,该引擎支持XSLT 1.0或2.0脚本

具体来说,将数据导出到原始xml文件(无映射)中,并使用XSLT 1.0或更简单的XSLT 2.0。我将两者都包括在内,因为为了可移植性,XSLT1.0在其他语言库(Java、Python、PHP、R)中被更广泛地用作默认规范,以防您需要在SAS之外运行或将来的读者使用早期版本

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

XSLT1.0(另存为.xsl文件)

输出

<?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>

1.
2.
file1.txt
file2.txt

听起来像是您希望通过组处理来完成。您是否尝试将BY语句添加到数据步骤中?我认为问题可能在于文件/文件不是COL1/COL2的父级;我认为您必须以不同的方式使用XPATH。但我不确定具体怎么做。地图需要指出COL1和COL2是“文件”的“兄弟”,基本上不是“文件”的。再看看这个,我认为SAS不会通过XML地图路径生成您想要的文件。对于它来说,没有一个好的方法可以按顺序进行操作,这就是SAS在这种情况下的工作方式。如果将COL1和COL2设置为文件的属性,则可以让它或多或少地按照您的预期工作,但不能以其他方式工作。@Tom,我不认为“数据步骤帮助”中的“分组”可以吗?XMLMAP指导整个过程……如果数据具有多个分组,例如此分组--
值('3','4','file3.txt')值('3','4','file4.txt')
,则生成的xml将不会提供组的清晰分隔。每个小组是否应该有自己的
?@Richard,这是一个OP问题。我认为
是根,col值是分组。现在我可以调整分组标准,如果OP只需要COL作为更高的节点和嵌套节点中的文件。哇,谢谢!今天晚些时候我会调查一下,然后再打给你。同时还有一些额外的细节:
是根标记,其中只有一个
基本上只是根标记的“属性”,尽管它们表示为自己的元素。所以基本上只有一个
和一个
标签,里面有所有单独的文件。太棒了!我总是抓住机会使用XSLT,如果在SAS中使用XSLT的话,我很少这样做!很高兴提供帮助。因此,理想情况下,COL1和COL2实际上是
@
属性,但您所使用的格式不支持这一点。太糟糕了,那就微不足道了。回答得不错@Parfait,我不知道PROC-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>
<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>