我需要将所有属性转换为XML文件中的节点,但根节点中的属性除外。
我在这里找到了一个类似的问题:xquery to convert attributes to tags,但我需要在C#中进行转换。
我还在这里找到了一个使用XLS的可能解决方案:Convert attribute value into element。但是,该解决方案实质上将节点名称更改为属性名称并删除属性。
我需要使用属性的名称和值创建新的兄弟节点并删除属性,但仍保留包含属性的节点。
给出以下XML:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment Name="Test">
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value Source="Literal">O</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value Source="Calculated" Initial="1">Sequence</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value Source="Property">BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
我需要生成以下XML:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment>
<Name>Test</Name>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
<Source>Literal</Source>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
<Source>Calculated</Source>
<Initial>1</Initial>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
<Source>Property</Source>
</SegmentField>
</Segment>
</Segments>
</Something>
我编写了以下方法来创建一个新的XML对象,从source
元素的属性创建新元素:
private static XElement ConvertAttribToElement(XElement source)
{
var result = new XElement(source.Name.LocalName);
if (source.HasElements)
{
foreach (var element in source.Elements())
{
var orphan = ConvertAttribToElement(element);
result.Add(orphan);
}
}
else
{
result.Value = source.Value.Trim();
}
if (source.Parent == null)
{
// ERROR: The prefix '' cannot be redefined from '' to 'http://www.something.com' within the same start element tag.
//foreach (var attrib in source.Attributes())
//{
// result.SetAttributeValue(attrib.Name.LocalName, attrib.Value);
//}
}
else
{
while (source.HasAttributes)
{
var attrib = source.LastAttribute;
result.AddFirst(new XElement(attrib.Name.LocalName, attrib.Value.Trim()));
attrib.Remove();
}
}
return result;
}
此方法生成以下XML:
<Something>
<Version>4.0.8</Version>
<Segments>
<Segment>
<Name>Test</Name>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>
<Source>Literal</Source>O</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>
<Source>Calculated</Source>
<Initial>1</Initial>Sequence</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>
<Source>Property</Source>BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
输出有两个直接问题:
1)根元素中的属性丢失
2)'Value'元素的属性被创建为子元素而不是兄弟元素。
为了解决第一个问题,我尝试将source
元素的属性分配给result
元素,但是这导致无法从''重新定义“前缀''在同一个启动元素标记中的'http://www.something.com'错误。我注释掉了导致错误的代码。
为了解决第二个问题,我尝试将从属性创建的元素添加到source.Parent
元素,但这导致新元素根本不显示。
我还重写了直接在source
元素上运行的方法:
private static void ConvertAttribToElement2(XElement source)
{
if (source.HasElements)
{
foreach (var element in source.Elements())
{
ConvertAttribToElement2(element);
}
}
if (source.Parent != null)
{
while (source.HasAttributes)
{
var attrib = source.LastAttribute;
source.Parent.AddFirst(new XElement(attrib.Name.LocalName, attrib.Value.Trim()));
attrib.Remove();
}
}
}
重写产生了以下XML:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Name xmlns="">Test</Name>
<Segment>
<SegmentField>
<Source xmlns="">Literal</Source>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
</SegmentField>
<SegmentField>
<Source xmlns="">Calculated</Source>
<Initial xmlns="">1</Initial>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
</SegmentField>
<SegmentField>
<Source xmlns="">Property</Source>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
重写确实解决了保留根元素属性的第一个问题。它还部分解决了第二个问题,但产生了一个新问题:新元素具有空白的xmlns属性。
答案 0 :(得分:2)
使用此方法将Xml属性转换为xml节点:
public static void ReplaceAttributesByNodes(XmlDocument document, XmlNode node)
{
if (document == null)
{
throw new ArgumentNullException("document");
}
if (node == null)
{
throw new ArgumentNullException("node");
}
if (node.HasChildNodes)
{
foreach (XmlNode tempNode in node.ChildNodes)
{
ReplaceAttributesByNodes(document, tempNode);
}
}
if (node.Attributes != null)
{
foreach (XmlAttribute attribute in node.Attributes)
{
XmlNode element = document.CreateNode(XmlNodeType.Element, attribute.Name, null);
element.InnerText = attribute.InnerText;
node.AppendChild(element);
}
node.Attributes.RemoveAll();
}
}
//how to use it
static void Main()
{
string eventNodeXPath = "Something/Segments/Segment";//your segments nodes only
XmlDocument document = new XmlDocument();
document.Load(@"your playlist file full path");//your input playlist file
XmlNodeList nodes = document.SelectNodes(eventNodeXPath);
if (nodes != null)
{
foreach (XmlNode node in nodes)
{
ReplaceAttributesByNodes(document, node);
}
}
doc.Save("your output file full path");
}
答案 1 :(得分:2)
您可以构建一个扩展方法来展平每个元素:
public static IEnumerable<XElement> Flatten(this XElement element)
{
// first return ourselves
yield return new XElement(
element.Name,
// Output our text if we have no elements
!element.HasElements ? element.Value : null,
// Or the flattened sequence of our children if they exist
element.Elements().SelectMany(el => el.Flatten()));
// Then return our own attributes (that aren't xmlns related)
foreach (var attribute in element.Attributes()
.Where(aa => !aa.IsNamespaceDeclaration))
{
// check if the attribute has a namespace,
// if not we "borrow" our element's
var isNone = attribute.Name.Namespace == XNamespace.None;
yield return new XElement(
!isNone ? attribute.Name
: element.Name.Namespace + attribute.Name.LocalName,
attribute.Value);
}
}
您可以使用它:
public static XElement Flatten(this XDocument document)
{
// used to fix the naming of the namespaces
var ns = document.Root.Attributes()
.Where(aa => aa.IsNamespaceDeclaration
&& aa.Name.LocalName != "xmlns")
.Select(aa => new { aa.Name.LocalName, aa.Value });
return new XElement(
document.Root.Name,
// preserve "specific" xml namespaces
ns.Select(n => new XAttribute(XNamespace.Xmlns + n.LocalName, n.Value)),
// place root attributes right after the root element
document.Root.Attributes()
.Where(aa => !aa.IsNamespaceDeclaration)
.Select(aa => new XAttribute(aa.Name, aa.Value)),
// then flatten our children
document.Root.Elements().SelectMany(el => el.Flatten()));
}
这会产生你所指示的输出,但xsi:schemaLocation
属性除外,这是我发现的问题。它选择一个默认的命名空间名称(p1
),但最终它可以工作。
产生以下内容:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
<Source>Literal</Source>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
<Source>Calculated</Source>
<Initial>1</Initial>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
<Source>Property</Source>
</SegmentField>
</Segment>
<Name>Test</Name>
</Segments>
</Something>
答案 2 :(得分:2)
此XSLT转换:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.something.com">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vNamespace" select="namespace-uri(/*)"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/*/@*">
<xsl:element name="{name()}" namespace="{$vNamespace}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="x:Value">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
<xsl:apply-templates select="@*"/>
</xsl:template>
</xsl:stylesheet>
应用于提供的XML文档:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment Name="Test">
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value Source="Literal">O</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value Source="Calculated" Initial="1">Sequence</Value>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value Source="Property">BarCode</Value>
</SegmentField>
</Segment>
</Segments>
</Something>
产生完全正确的结果:
<Something xmlns="http://www.something.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.xomething.com segments.xsd">
<Version>4.0.8</Version>
<Segments>
<Segment>
<Name>Test</Name>
<SegmentField>
<SegmentIndex>0</SegmentIndex>
<Name>RecordTypeID</Name>
<Value>O</Value>
<Source>Literal</Source>
</SegmentField>
<SegmentField>
<SegmentIndex>1</SegmentIndex>
<Name>OrderSequenceNumber</Name>
<Value>Sequence</Value>
<Source>Calculated</Source>
<Initial>1</Initial>
</SegmentField>
<SegmentField>
<SegmentIndex>3</SegmentIndex>
<Name>InstrumentSpecimenID</Name>
<Value>BarCode</Value>
<Source>Property</Source>
</SegmentField>
</Segment>
</Segments>
</Something>
<强>解释强>:
身份规则/模板“按原样”复制每个节点。
身份规则由两个模板覆盖 - 一个匹配任何不是文档顶部元素的元素的任何属性,另一个匹配任何Value
元素。
模板匹配属性(第一个覆盖模板)代替属性创建一个与匹配属性具有相同本地名称和值的元素。此外,元素名称与文档顶部元素所属的名称空间相同(这样可以避免xmlns=""
)。
匹配任何Value
元素的模板会复制它并处理其子树(后代节点),然后处理其属性。通过这种方式,从属性生成的元素将成为兄弟姐妹,而不是Value
元素的子元素。