2

我一直在使用示例 XMLExtractor(从https://github.com/Azure/usql/tree/master/Examples/DataFormats克隆)从我的 xml 元素中提取属性。

如果根元素具有任何已定义的属性,则提取器将无法工作。

例如,我需要从以下 XML 文件中获取“rec”元素的“sTime”属性:

<lics xmlns="***" lVer="*" pID="*" aKey="*" cTime="*" gDel="*" country="*" fStr="*">
   <rec Ver="*" hID="*.*.*" cSID="Y5/*=" uID="*\Rad.*" uSID="*/*=" cAttrs="*" sTime="*" eTime="*" projID="*" docID="*" imsID="*">
   </rec>
</lics>

使用以下 U-SQL 脚本:

@e = EXTRACT a string, b string
 FROM @"D:\file.xml"
 USING new Microsoft.Analytics.Samples.Formats.Xml.XmlDomExtractor(rowPath:"rec",
                         columnPaths:new SQL.MAP<string, string> { {"@sTime", "a"} });

OUTPUT @e TO "D:/output.csv" USING Outputters.Csv(quoting:false);

这将写入一个空文件。但是,如果我删除“lics”标签的属性,它就可以工作。

<lics>
   <rec Ver="*" hID="*.*.*" cSID="Y5/*=" uID="*\Rad.*" uSID="*/*=" cAttrs="*" sTime="*" eTime="*" projID="*" docID="*" imsID="*">
   </rec>
</lics>

这是提取器的问题吗?或者这需要在提取器的任何参数中定义吗?

4

2 回答 2

3

问题是Microsoft.Analytics.Samples.Formats.Xml.XmlDomExtractor完全忽略了 XML 命名空间。

更好的实现看起来像这样(虽然未经测试):

[SqlUserDefinedExtractor(AtomicFileProcessing = true)]
public class XmlDomExtractorNs : IExtractor
{
    private string rowPath;
    private SqlMap<string, string> columnPaths;
    private string namespaces;
    private Regex xmlns = new Regex("(?:xmlns:)?(\\S+)\\s*=\\s*([\"']?)(\\S+)\\2");

    public XmlDomExtractor(string rowPath, SqlMap<string, string> columnPaths, string namespaces)
    {
        this.rowPath = rowPath;
        this.columnPaths = columnPaths;
        this.namespaces = namespaces;
    }

    public override IEnumerable<IRow> Extract(IUnstructuredReader input, IUpdatableRow output)
    {
        IColumn column = output.Schema.FirstOrDefault(col => col.Type != typeof(string));
        if (column != null)
        {
            throw new ArgumentException(string.Format("Column '{0}' must be of type 'string', not '{1}'", column.Name, column.Type.Name));
        }

        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.Load(input.BaseStream);

        XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDocument.NameTable);
        if (this.namespaces != null)
        {
            foreach (Match nsdef in xmlns.Matches(this.namespaces))
            {
                string prefix = nsdef.Groups[1].Value;
                string uri = nsdef.Groups[3].Value;
                nsmgr.AddNamespace(prefix, uri);
            }
        }

        foreach (XmlNode xmlNode in xmlDocument.DocumentElement.SelectNodes(this.rowPath, nsmgr))
        {
            foreach(IColumn col in output.Schema)
            {
                var explicitColumnMapping = this.columnPaths.FirstOrDefault(columnPath => columnPath.Value == col.Name);
                XmlNode xml = xmlNode.SelectSingleNode(explicitColumnMapping.Key ?? col.Name, nsmgr);
                output.Set(explicitColumnMapping.Value ?? col.Name, xml == null ? null : xml.InnerXml);
            }
            yield return output.AsReadOnly();
        }
    }
}

并像这样使用:

@e = EXTRACT a string, b string
  FROM @"D:\file.xml"
  USING new Your.Namespace.XmlDomExtractorNs(
    rowPath:"lics:rec",
    columnPaths:new SQL.MAP<string, string> { {"@sTime", "a"} },
    namespaces:"lics=http://the/namespace/of/the/doc"
  );

OUTPUT @e TO "D:/output.csv" USING Outputters.Csv(quoting:false);

namespaces 参数将被解析为 namespace-prefix 和 namespace-uri 部分,然后将用于驱动 XPath 查询。为方便起见,它支持以下任何值格式:

  • 'xmlns:foo="http://uri/1" xmlns:bar="http://uri/2"'
  • "xmlns:foo='http://uri/1' xmlns:bar='http://uri/2'"
  • "xmlns:foo=http://uri/1 xmlns:bar=http://uri/2"
  • "foo=http://uri/1 bar=http://uri/2"

因此它可以直接从 XML 源中复制它们,也可以手动创建它们而不会大惊小怪。

由于您使用的 XML 文档具有默认名称空间,并且 XPath 要求对表达式中所需的任何名称空间使用前缀,因此您必须为名称空间 URI 选择名称空间前缀。我选择使用lics上面。


FWIW,解析命名空间参数的正则表达式分解如下:

(?: # 非捕获组
  xmlns: # 文字“xmlns:”
)? # 结束非捕获组,设为可选
(\S+) # GROUP 1(前缀):任意数量的非空白字符
\s*=\s* # 文字“=”可选地被空格包围
(["']?) # GROUP 2(分隔符):单引号或双引号,可选
(\S+) # GROUP 3 (uri): 任意数量的非空白字符
\2 # 组 2 中的任何内容以结束命名空间 URI
于 2016-02-01T14:54:26.250 回答
1

我可能会使用另一个 SQL.MAP 来定义名称空间映射的前缀(并且不需要与文档中相同的前缀)。

我在这里创建了一个功能请求:https ://feedback.azure.com/forums/327234-data-lake/suggestions/11675604-add-xml-namespace-support-to-xml-extractor 。请添加您的投票。

更新:XmlDomExtractor 现在支持 XML 命名空间。使用以下USING子句:

 USING new Microsoft.Analytics.Samples.Formats.Xml.XmlDomExtractor(rowPath:"ns:rec",
                     columnPaths:new SQL.MAP<string, string> { {"@sTime", "a"} },
                     namespaceDecls: new SqlMap<string,string>{{"ns","***"}});
于 2016-02-01T18:47:12.507 回答