C # для преобразования атрибутов XML в элементы - PullRequest
4 голосов
/ 27 июня 2011

Мне нужно преобразовать все атрибуты в узлы в файле XML, за исключением атрибутов в корневом узле.

Я нашел похожий вопрос здесь: xquery для преобразования атрибутов в теги , но мне нужно выполнить преобразование в C #.

Я также нашел возможное решение с использованием XLS здесь: Преобразовать значение атрибута в элемент . Однако это решение существенно меняет имя узла на имя атрибута и удаляет атрибут.

Мне нужно создать новые узлы-братья с именем и значением атрибутов и удалить атрибуты, но при этом сохранить узел, содержащий атрибуты.

С учетом следующего 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) Атрибуты из элемента «Значение» создаются как дочерние элементы вместо родных элементов.

Чтобы решить первую проблему, я попытался присвоить атрибуты элемента 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>

Перезапись did решает первую проблему сохранения атрибутов корневого элемента. Это также частично решило вторую проблему, но породило новую проблему: новые элементы имеют пустой атрибут xmlns.

Ответы [ 3 ]

2 голосов
/ 27 июня 2011

Вы можете создать метод расширения, чтобы сгладить каждый элемент:

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()));
}

Это производит вывод, как вы указали, за исключением *Атрибут 1007 *, который, как я обнаружил, проблематичен.Он выбирает имя пространства имен по умолчанию (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 голосов
/ 28 июня 2011

Это 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>

Объяснение :

  1. Правило / шаблон идентификации копирует каждый узел "как есть".

  2. Правило идентификации переопределяется двумя шаблонами - один соответствует любому атрибуту любого элемента, который не является верхним элементом документа, другой - любому элементу Value.

  3. Атрибуты соответствия шаблону (первый переопределяющий шаблон) создают вместо атрибута элемент с тем же локальным именем и значением, что и у сопоставленного атрибута.Кроме того, имя элемента помещается в то же пространство имен, к которому принадлежит верхний элемент документа (это исключает xmlns="").

  4. Шаблон, соответствующий любому Value элемент копирует его и обрабатывает все его поддерево (дочерние узлы), затем обрабатывает его атрибуты.Таким образом, элементы, сгенерированные из атрибутов, становятся братьями и сестрами, а не потомками элемента Value.

2 голосов
/ 27 июня 2011

Используйте этот метод для преобразования атрибутов 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");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...