【问题标题】:Modifying the datatype in SAS XML Map File修改 SAS XML 映射文件中的数据类型
【发布时间】:2020-03-19 17:40:31
【问题描述】:

在SAS中读取XML数据时需要控制数据类型。使用 SAS 中的 XML libname 引擎写入和访问 XML 数据。

XML 文件:

<Test>
   <origin>YYYY</origin>
   <NumToUse>50503</NumToUse>
   <AcctNum>3-219HHJLJ</AcctNum>
   <Status>1</Status>
   <TADIG>AUSVF</TADIG>
   <LocationNumber>1234567891011</LocationNumber>
   <Phnumber>1234567890</Phnumber>
   <ReferenceNumber>0044E71146</ReferenceNumber>

地图文件:

    <COLUMN name="LocationNumber">
        <PATH syntax="XPath">/Test/LocationNumber</PATH>
        <TYPE>character</TYPE>
        <DATATYPE>string</DATATYPE>
        <LENGTH>11</LENGTH>
    </COLUMN>

    <COLUMN name="PhNumber">
        <PATH syntax="XPath">/Test/PhNumber</PATH>
        <TYPE>character</TYPE>
        <DATATYPE>string</DATATYPE>
        <LENGTH>15</LENGTH>
    </COLUMN>

    <COLUMN name="ReferenceNumber">
        <PATH syntax="XPath">/Test/ReferenceNumber</PATH>
        <TYPE>numeric</TYPE>
        <DATATYPE>double</DATATYPE>
    </COLUMN>

由于参考编号被视为数字,因此无法获得该特定列的值。它给了我

ERROR: Data contains invalid content for float datatype. Invalid content is 0044E71146

如何将数据读入 SAS 数据集?请建议

【问题讨论】:

  • 正在使用 SAS XML Engine FILENAME XMLFILE "/v/temp/test.xml" 创建 MAP 文件;文件名 MAPFILE "/v/temp/test.xml.map" ; LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPFILE AUTOMAP=REUSE ;
  • 让 SAS 一次创建地图文件。然后编辑它并解决问题。保存固定版本并从现在开始将其用于此类 XML 文件。

标签: sas


【解决方案1】:

XMLV2 库创建的映射文件可以在使用 libref 将数据复制到 SAS 会话之前进行修改。

引擎生成的地图文件(本身就是一个xml文件)的处理方式有很多种

  • XSL 转换 (Proc XSL)
    • 精通 XSLT 语言的人(不是我)可能会编写一个简短的程序来执行修改
  • 已解析的 xml 文档的编程操作
  • xml 文件的文本操作
    • 手工编辑
    • 文本处理程序

地图文件

  • 定义哪些 xml 节点将成为数据集列
    • 定义列的类型、长度、格式、标签等
  • 定义要创建的表
  • 定义哪些表要包含哪些列

对于想要将 XMLV2 引擎对数字列的解释更改为字符列的情况,需要修改映射文件(又名 transformed)。

  • &lt;COLUMN&gt; 节点具有至少需要更改的子节点
    来自
    &lt;TYPE&gt;numeric&lt;/TYPE&gt;

    &lt;TYPE&gt;character&lt;/TYPE&gt;

注意事项

在将数字转换为字符集时,您可能还需要考虑其他一些因素,例如:

  • 这个数字是日期吗?
  • 是否应该先通过其格式呈现数字?

SAS 用户对包含数据和元数据(在标题中)的单一“数据集”的概念感到满意。对于 XML 数据,数据在一个文件中,而元数据(映射文件)在另一个文件中。

当导出的 SAS 数据仅保留数据 xml 时,元数据会丢失。往返 SAS 意味着必须通过自动映射来猜测元数据。

“已解析的 xml 文档的编程操作”示例代码

此示例将数字列的所有列定义更改为字符。 XPath /SXLEMAP/TABLE/COLUMN[not(@class='ORDINAL') and ./TYPE[text()='numeric'] 用于标识将要更改的列定义。没有进一步的特殊考虑。

创建要处理的xml数据文件


%macro createXmlV2(data=,folder=);
  %local lib mem;

  %let syslast = &data;

  %let lib = %scan(&syslast,1,.);
  %let mem = %scan(&syslast,2,.);

  FILENAME XMLOUT  "&folder./&data..xml";

  LIBNAME XMLOUT XMLV2;

  proc copy in=&lib out=xmlout;
    select &mem;
  run;

  LIBNAME XMLOUT clear;
  FILENAME XMLOUT clear;
%mend;

%* Something to play with;
%* Create an XMLV2 generated xml file containing the data set;

%createXmlV2 (data=sashelp.citiday, folder=/temp)
%createXmlV2 (data=sashelp.citimon, folder=/temp)
%createXmlV2 (data=sashelp.baseball, folder=/temp)

%*;

转换自动映射文件,使数字列变为字符列

%macro prepXmlRefsFor(file=);
FILENAME XMLFILE "&file";
FILENAME MAPOUT  "&file..map";
FILENAME MAPOUT2 "&file..map.transformed";
%mend;

%prepXmlRefsFor(file=/temp/sashelp.citiday.xml)
%prepXmlRefsFor(file=/temp/sashelp.baseball.xml)
%prepXmlRefsFor(file=/temp/sashelp.citimon.xml)

%*;

%* create an automap file using XMLV2 library engine;

LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPOUT AUTOMAP=REPLACE ;

%* parse and rewrite the generated map file ;
%* change ALL non-ordinal, non-character COLUMN nodes to indicate character type wanted;

proc groovy;
submit 
  "%sysfunc(pathname(MAPOUT))" 
  "%sysfunc(pathname(MAPOUT2))"
  "20" 
;
import javax.xml.parsers.*;
import javax.xml.xpath.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

// get parameter from submit line;

map_in=args[0];   // the automap
map_out=args[1];  // the automap transformed
length=args[2];   // length of character value for columns previously considered numeric

// parse mapfile;

doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(map_in);

xPath = XPathFactory.newInstance().newXPath();

// select the set of automap nodes that define non-ordinal, numeric columns

columns = xPath.evaluate(
  "/SXLEMAP/TABLE/COLUMN" +
  "[" +
    "not(@class='ORDINAL')" +
    " and " +
    "./TYPE[text()='numeric']" +
  "]", doc, XPathConstants.NODESET);

for (column in columns) {
  type = xPath.evaluate("TYPE",     column, XPathConstants.NODE);
  dtyp = xPath.evaluate("DATATYPE", column, XPathConstants.NODE);
  leng = xPath.evaluate("LENGTH",   column, XPathConstants.NODE);

  type.setTextContent("character");
  dtyp.setTextContent("string");

  if (leng == null)
    column.appendChild(leng = doc.createElement("LENGTH"));

  leng.setTextContent(length);
}

// rewrite mapfile with updated nodes

TransformerFactory.newInstance().newTransformer().transform(
  new DOMSource(doc), new StreamResult(new File(map_out))
);

println "Programmatic transformation of mapfile completed.";
endsubmit;
quit;

* resubmit libname so libref uses transformed mapfile;

LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPOUT2 AUTOMAP=REUSE;

proc copy in=xmlfile out=work;
run;

LIBNAME XMLFILE clear;
FILENAME XMLFILE clear;
FILENAME MAPOUT clear;
FILENAME MAPOUT2 clear;

在检查“往返”结果后变得显而易见的是,XMLV2创建的 xml 文件在重新读取时将创建 单独列包含缺失值的任何列的命名表。必须合并这些表才能重新创建原始数据集。

【讨论】:

  • 非常感谢!!很容易理解
【解决方案2】:

您可能会理解,XMLV2 引擎中内置的自动映射功能选择将 ReferenceNumber 定义为数字而不是字符,这是因为解析器正在检查的唯一一个值是 0044E71146,并且假定 @ 987654322@ 是数字的科学(或指数)表示法。

解决方案是让 libname 自动映射数据 xml 文件,然后更新映射文件 xml 以满足您的要求。

示例代码:

XMLV2引擎创建MAPFILEProc GROOVY用于XML解析和重写mapfile。

FILENAME XMLFILE "/temp/test.xml" ;
FILENAME MAPFILE "/temp/test.xml.map" ;

* parse data test.xml and write mapfile test.xml.map;
LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPFILE AUTOMAP=REPLACE ;

* parse and rewrite mapfile;
* change desired column nodes to be string/character of a specified length;

proc groovy;
submit "%sysfunc(pathname(mapfile))";
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

* get parameter from submit line;

mapfile=args[0];

* parse mapfile;

doc = DocumentBuilderFactory
      .newInstance()
      .newDocumentBuilder()
      .parse(
        mapfile
      )
      ;

xPath = XPathFactory
        .newInstance()
        .newXPath()
        ;

void setCharacter(column,length) {

  * find column node and child nodes important to XMLV2 mapfile usage;

  node = xPath.evaluate("/SXLEMAP/TABLE/COLUMN[@name='"+column+"']", doc, XPathConstants.NODE);
  type = xPath.evaluate("TYPE",     node, XPathConstants.NODE);
  dtyp = xPath.evaluate("DATATYPE", node, XPathConstants.NODE);
  leng = xPath.evaluate("LENGTH",   node, XPathConstants.NODE);

  if (type != null && !type.getTextContent().equals("character")) { type.setTextContent("character") } 
  if (dtyp != null && !dtyp.getTextContent().equals("string"))    { dtyp.setTextContent("string") } 
  if (leng == null) {
    leng = doc.createElement("LENGTH");
    leng.setTextContent(length.toString());
    node.appendChild(leng);
  }
  else
  if (!length.getTextContent().equals(length.toString())) {
    leng.setTextContent(length.toString());
  }
}

// Make sure these two columns will be character, if not already

setCharacter("ReferenceNumber",25);
setCharacter("Phnumber", 20);

// rewrite mapfile with updated nodes

TransformerFactory
.newInstance()
.newTransformer()
.transform(
  new DOMSource(doc),
  new StreamResult(new File(mapfile))
);

endsubmit;
quit;

* resubmit libname so libref uses now updated mapfile;

LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPFILE;

proc copy in=xmlfile out=work;
run;

注意:您可以对地图文件进行文本解析和重写,但是,地图文件可能无法满足您的“文本解析”期望。

【讨论】:

  • 在你前面提到的节点 = xPath.evaluate("/SXLEMAP/TABLE/COLUMN[@name='"+column+"']", doc, XPathConstants.NODE); /SXLEMAP/TABLE/COLUMN 是什么?我可以直接在我的代码中使用它吗?抱歉,我是 proc groovy 的新手
  • 而不是setcharacter硬编码列名,是否可以将所有标签更改为字符?
  • 是的,XPath 的强大功能可以返回满足路径条件/SXLEMAP/TABLE/COLUMN 的节点列表。
  • 查看 XMLV2 创建的 MAPFILE。到列说明符的节点嵌套或层次下降定义了“XPath”。所以 SXLEMAP 有孩子 TABLE 有孩子 COLUMN。
  • 谢谢,我明白了。有什么办法可以循环遍历创建的 MAP 文件中的所有元素并更改为 STRING?
【解决方案3】:

让 SAS 制作地图文件。

FILENAME XMLFILE "/v/temp/test.xml" ; 
FILENAME MAPFILE "/v/temp/test.xml.map" ; 
LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPFILE AUTOMAP=REUSE ; 

编辑文件并修正定义

<COLUMN name="ReferenceNumber">
    <PATH syntax="XPath">/Test/ReferenceNumber</PATH>
    <TYPE>character</TYPE>
    <DATATYPE>string</DATATYPE>
    <LENGTH>15</LENGTH>
</COLUMN>

您可能希望将其保存在永久位置而不是临时位置。现在使用fixed文件重新定义libref。

FILENAME XMLFILE "/v/temp/test.xml" ; 
FILENAME MAPFILE "/v/permanent/fixed_xml.map" ; 
LIBNAME XMLFILE XMLV2 XMLTYPE=XMLMAP XMLMAP=MAPFILE AUTOMAP=REUSE ; 

【讨论】:

  • 非常感谢,如果知道列名就可以了,对我来说 XML 是动态的,每次运行时列名都会改变
  • XML 的生成器没有为您提供理解 XML 所需的元数据吗?这是关键信息,尤其是当 XML 随时间变化时。
  • 不,他们没有给我们 XSD \
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多