修改SAS XML映射文件中的数据类型



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

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数据集?建议请

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

有很多方法可以处理引擎生成的映射文件(它本身就是一个xml文件(

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

映射文件:

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

如果希望将XMLV2引擎对数字列的解释更改为字符列,则需要修改映射文件(也称为转换(。

  • <COLUMN>节点具有子节点,这些子节点至少需要将

    <TYPE>numeric</TYPE>
    更改为
    <TYPE>character</TYPE>

注意事项

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

  • 这是数字日期吗
  • 应该先通过数字的格式来渲染数字吗

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文件在重新读取时,将为任何包含缺失值的列创建单独的列命名表。必须合并这些表才能重新创建原始数据集。

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

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

示例代码:

XMLV2引擎创建MAPFILEProc GROOVY用于XML解析和重写映射文件。

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;

注意:您可以对映射文件进行文本解析和重写,但是,映射文件可能无法满足您的"文本解析"期望的可能性很小。

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

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

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

相关内容

最新更新