XElement의 InnerXml을 얻는 가장 좋은 방법은 무엇입니까?


147

body아래 코드에서 혼합 요소 의 내용을 얻는 가장 좋은 방법은 무엇입니까 ? 요소에 XHTML 또는 텍스트가 포함될 수 있지만 내용을 문자열 형식으로 원합니다. XmlElement유형은 가지고 InnerXml난 후 정확히 무엇 속성을.

작성된 코드는 거의 내가 원하는 않지만, 주변 포함 <body>... </body>내가 원하지 않는 요소를.

XDocument doc = XDocument.Load(new StreamReader(s));
var templates = from t in doc.Descendants("template")
                where t.Attribute("name").Value == templateName
                select new
                {
                   Subject = t.Element("subject").Value,
                   Body = t.Element("body").ToString()
                };

답변:


208

제안 된 솔루션 중 어떤 것이 가장 잘 수행되는지 확인하고 싶었 기 때문에 비교 테스트를 수행했습니다. 관심이 없으면 LINQ 메서드를 Greg가 제안한 일반 구형 System.Xml 메서드 와 비교했습니다 . 가장 느린 방법 은 가장 빠른 방법 보다 3 배 이상 느린 변형이 흥미롭고 예상했던 것과 다릅니다. .

결과는 가장 빠르거나 느리게 정렬됩니다.

  1. CreateReader-인스턴스 헌터 (0.113 초)
  2. 평범한 오래된 System.Xml-Greg Hurlman (0.134 초)
  3. 문자열 연결로 집계-Mike Powell (0.324 초)
  4. StringBuilder-Vin (0.333 초)
  5. String.Join on array-Terry (0.360 초)
  6. 배열의 문자열-Marcin Kosieradzki (0.364)

방법

20 개의 동일한 노드 ( '힌트'라고 함)가있는 단일 XML 문서를 사용했습니다.

<hint>
  <strong>Thinking of using a fake address?</strong>
  <br />
  Please don't. If we can't verify your address we might just
  have to reject your application.
</hint>

위의 초로 표시된 숫자는 20 개 노드의 "내부 XML"을 1000 회 연속으로 추출하여 평균 (평균) 5 회 실행 한 결과입니다. XML을로드하고 XmlDocument( System.Xml 메서드의 경우) 구문 분석하는 데 걸리는 시간은 포함하지 않았습니다.XDocument (다른 모든 것의 경우 .

내가 사용한 LINQ 알고리즘은 다음과 같습니다. (C #-모두 XElement"부모"를 취하고 내부 XML 문자열을 반환합니다)

리더 만들기 :

var reader = parent.CreateReader();
reader.MoveToContent();

return reader.ReadInnerXml();

문자열 연결로 집계 :

return parent.Nodes().Aggregate("", (b, node) => b += node.ToString());

StringBuilder :

StringBuilder sb = new StringBuilder();

foreach(var node in parent.Nodes()) {
    sb.Append(node.ToString());
}

return sb.ToString();

배열의 String.Join :

return String.Join("", parent.Nodes().Select(x => x.ToString()).ToArray());

배열의 String.Concat :

return String.Concat(parent.Nodes().Select(x => x.ToString()).ToArray());

노드에서 .InnerXml을 호출하기 때문에 여기에 "Plain old System.Xml"알고리즘을 표시하지 않았습니다.


결론

성능이 중요한 경우 (예 : 많은 XML, 자주 구문 분석) 매번 Daniel의 CreateReader방법을 사용 합니다 . 몇 가지 쿼리를 수행하는 경우 Mike의 더 간결한 집계 방법을 사용할 수 있습니다.

많은 노드 (아마도 100)가있는 큰 요소에서 XML을 사용하는 경우 아마도 StringBuilderAggregate 메서드 를 사용하는 것의 이점을 볼 수 있지만 over는 아닙니다 CreateReader. 큰 목록을 큰 배열로 변환하는 것에 대한 패널티 (여기서는 작은 목록에서는 명백 함)로 인해 이러한 조건에서 JoinConcat메소드가 더 효율적 이라고 생각하지 않습니다 .


StringBuilder 버전은 한 줄에 작성할 수 있습니다. var result = parent.Elements (). Aggregate (new StringBuilder (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString ( ))
Softlion

7
당신이 놓친 parent.CreateNavigator().InnerXml(필요 using System.Xml.XPath확장 방법을).
Richard

난 당신이 필요 생각하지 않았을 .ToArray()내부를 .Concat, 그러나 빨리 할 것
drzaus

경우에 이러한 답변의 하단으로 스크롤하지 않습니다 단지에서 컨테이너 / 뿌리를 제거 고려 .ToString()이 답변 . 더 빠른 것 같습니다 ...
drzaus

2
실제로 var reader = parent.CreateReader();using 문으로 감싸 야 합니다.
BrainSlugs83

70

나는 이것이 훨씬 더 나은 방법이라고 생각합니다 (VB에서는 번역하기가 어렵지 않아야 함).

XElement x가 주어지면 :

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml

좋은! 이것은 제안 된 다른 방법 중 일부보다 훨씬 빠릅니다 (모두 테스트했습니다. 자세한 내용은 답변을 참조하십시오). 모두 작업을 수행하지만 System.Xml.Node.InnerXml 자체보다 더 빠릅니다.
Luke Sampson

4
XmlReader는 일회용이므로 사용하여 포장하는 것을 잊지 마십시오 (VB를 알고 있다면 직접 답을 편집 할 것입니다).
Dmitry Fedorkov

19

XElement에서이 "확장"방법을 사용하는 것은 어떻습니까? 나를 위해 일했다!

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();

    foreach (XNode node in element.Nodes())
    {
        // append node's xml string to innerXml
        innerXml.Append(node.ToString());
    }

    return innerXml.ToString();
}

또는 Linq를 조금 사용하십시오

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();
    doc.Nodes().ToList().ForEach( node => innerXml.Append(node.ToString()));

    return innerXml.ToString();
}

참고 : 위 코드는 element.Nodes()반대로 사용해야 합니다 element.Elements(). 둘 사이의 차이점을 기억하는 것이 매우 중요합니다. element.Nodes()당신처럼 모든 것을 제공 XText, XAttribute등,하지만 XElement단지 요소.


15

최상의 접근 방식을 발견하고 입증 한 사람들에게 모든 정당한 인정을 받았으므로 (감사합니다!) 확장 방법으로 싸여 있습니다.

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}

10

간단하고 효율적으로 유지하십시오.

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • 집계는 문자열을 연결할 때 메모리 및 성능 비효율적입니다
  • Join ( "", sth)을 사용하면 Concat보다 두 배 더 큰 문자열 배열을 사용하고 있습니다. 코드에서 매우 이상하게 보입니다.
  • + =를 사용하는 것은 매우 이상해 보이지만 '+'를 사용하는 것보다 그리 나쁘지는 않습니다. 아마도 동일한 코드에 최적화 될 것입니다. 할당 결과가 사용되지 않고 컴파일러에 의해 안전하게 제거 될 수 있습니다.
  • StringBuilder는 매우 필수적입니다. 모든 사람은 불필요한 "상태"가 짜증나다는 것을 알고 있습니다.

7

나는 이것을 사용하여 끝났다.

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());

그것은 많은 문자열 연결을 할 것입니다-Vin의 StringBuilder 사용을 선호합니다. foreach 매뉴얼은 부정적이지 않습니다.
Marc Gravell

이 메소드는 오늘 날 정말로 저를 구했고, 새로운 생성자로 XElement를 작성하려고 시도했지만 다른 메소드 중 어느 것도 직접 빌려주지 않았습니다. 감사!
delliottg

3

개인적으로 InnerXmlAggregate 메소드를 사용하여 확장 메소드를 작성했습니다 .

public static string InnerXml(this XElement thiz)
{
   return thiz.Nodes().Aggregate( string.Empty, ( element, node ) => element += node.ToString() );
}

내 클라이언트 코드는 이전 System.Xml 네임 스페이스와 마찬가지로 간결합니다.

var innerXml = myXElement.InnerXml();

2

@Greg : 답변이 완전히 다른 답변으로 수정 된 것 같습니다. 내 대답은 그렇습니다 .System.Xml을 사용 하여이 작업을 수행 할 수는 있지만 LINQ to XML로 발을 젖게하고 싶습니다.

다른 사람이 왜 XElement의 .Value 속성을 사용하여 필요한 것을 얻을 수 없는지 궁금해하는 경우를 대비하여 원래의 회신을 남겨 두겠습니다.

@Greg : Value 속성은 모든 자식 노드의 모든 텍스트 내용을 연결합니다. 따라서 body 요소에 텍스트 만 포함되어 있으면 작동하지만 XHTML이 포함되어 있으면 모든 텍스트가 함께 연결되지만 태그는 없습니다.


나는이 똑같은 문제로 실행하고 버그라고 생각 : 나는 '혼합'내용 (즉했다 <root>random text <sub1>child</sub1> <sub2>child</sub2></root>가되었다) random text childchild를 통해를XElement.Parse(...).Value
drzaus

1

// 정규식을 사용하면 시작 및 끝 요소 태그를 간단하게 다듬을 수 있습니다.

var content = element.ToString();
var matchBegin = Regex.Match(content, @"<.+?>");
content = content.Substring(matchBegin.Index + matchBegin.Length);          
var matchEnd = Regex.Match(content, @"</.+?>", RegexOptions.RightToLeft);
content = content.Substring(0, matchEnd.Index);

1
산뜻한. 훨씬 더 빨리 사용하기 IndexOf:var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus


0

LINQ 대신 System.Xml 네임 스페이스 개체를 사용하여 작업을 수행 할 수 있습니까? 이미 언급했듯이 XmlNode.InnerXml이 정확히 필요한 것입니다.


0

궁금해 (b + =를 없애고 b + 만 있음)

t.Element( "body" ).Nodes()
 .Aggregate( "", ( b, node ) => b + node.ToString() );

보다 약간 덜 효율적일 수 있습니다

string.Join( "", t.Element.Nodes()
                  .Select( n => n.ToString() ).ToArray() );

100 % 확실 ...하지만 반사판 집계 ()와 string.Join ()에서이기는 ... 나는 생각한다 반환 값을 추가하는 Aggregate로 읽은 것으로 합니다.

문자열 = 문자열 + 문자열

여기에 FastStringAllocation이나 다른 내용이 언급되어있어 Microsoft 직원이 성능을 향상시킬 수 있습니다. 물론 내 .ToArray ()는 그것을 부정이라고 부르지 만 다른 제안을 제안하고 싶었습니다.


0

당신은 알고 있습니까? 가장 좋은 방법은 CDATA로 돌아가는 것입니다. (여기에서 솔루션을보고 있지만 CDATA는 가장 간단하고 저렴하지만 tho로 개발하기가 가장 편리하지 않다고 생각합니다.


0
var innerXmlAsText= XElement.Parse(xmlContent)
                    .Descendants()
                    .Where(n => n.Name.LocalName == "template")
                    .Elements()
                    .Single()
                    .ToString();

당신을 위해 일을 할 것인가


-2
public static string InnerXml(this XElement xElement)
{
    //remove start tag
    string innerXml = xElement.ToString().Trim().Replace(string.Format("<{0}>", xElement.Name), "");
    ////remove end tag
    innerXml = innerXml.Trim().Replace(string.Format("</{0}>", xElement.Name), "");
    return innerXml.Trim();
}

또한 요소에 속성이 있거나 공간이 너무 많으면 논리가 실패합니다.
Christoph
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.