LINQ to XML in Depth (3) Manipulating XML

[LINQ via C# series]

[LINQ to XML in Depth series]

Besides creating and querying XML, LINQ to XML also provides APIs for other XML manipulations, including cloning, deleting, replacing, and updating XML structures:

·        Clone

o     Explicit Clone: constructors of XAttribute, XCData, XComment, XDeclaration, XDocument, XElement, XProcessingInstruction, XText

·        Add

o    Add annotations: XObject.AddAnnotation

o    Add children: XContainer.Add, XContainer.AddFirst, XStreamingElement.Add

o    Add siblings: XNode.AddAfterSelf, XNode.AddBeforeSelf

·        Delete

o     Delete annotations: XObject.RemoveAnnotations

o     Delete attributes: XElement.RemoveAttributes, XAttribute.Remove

o     Delete self: XNode.Remove

o     Delete children: XContainer.RemoveNodes, XElement.RemoveAll

·        Replace

o     Replace attributes: XElement.ReplaceAttributes

o     Replace self: XNode.ReplaceWith

o     Replace children: XContainer.ReplaceNodes, XElement.ReplaceAll

·        Update

o     Update attribute: XAttribute.Value

o     Update comment: XComment.Value

o     Update declaration: XDeclaration.Encoding, XDeclaration.Standalone, XDeclaration.Version

o     Update document: XDocument.XDeclaration, XDocumentType.InternalSubset, XDocumentType.Name, XDocumentType.PublicId, XDocumentType.SystemId

o     Update element: XElement.Name, XElement.Value, XElement.SetAttributeValue, XElement.SetElementValue, XElement.SetValue

.NET Framework also provides APIs for validating and transforming XML:

·        Validate with XSD

o     Query schema: XAttribute.GetSchemaInfo*, XElement.GetSchemaInfo*

o     Validate schema: XAttribute.Validate*, XDocument.Validate*, XElement.Validate*

·        Transform with XSL: XslCompiledTransform.Transform

The APIs with * are extension methods provided by System.Xml.Schema.Extensions.

Clone

Most structures can be cloned by calling their constructors with the source instance:

internal static void ExplicitClone()

{

    XElement sourceElement = XElement.Parse("<element />");

    XElement clonedElement = new XElement(sourceElement);


    XText sourceText = new XText("text");

    XText clonedText = new XText(sourceText);


    XDocument sourceDocument = XDocument.Load("https://weblogs.asp.net/dixin/rss");

    XDocument clonedDocument = new XDocument(sourceDocument);

    object.ReferenceEquals(sourceDocument, clonedDocument).WriteLine(); // False

    object.Equals(sourceDocument, clonedDocument).WriteLine(); // False

    EqualityComparer<XDocument>.Default.Equals(sourceDocument, clonedDocument).WriteLine(); // False

    sourceDocument.Equals(clonedDocument).WriteLine(); // False

    (sourceDocument == clonedDocument).WriteLine(); // False

    XNode.DeepEquals(sourceDocument, clonedDocument).WriteLine(); // True

    XNode.EqualityComparer.Equals(sourceDocument, clonedDocument).WriteLine(); // True

}

If an XObject instance is in an XML tree, when it is added to a different XML tree, it is cloned, and the new instance is actually added to the target. The exceptions are XName and XNamespace, which are cached at runtime. For example:

internal static void ImplicitClone()

{

    XElement child = XElement.Parse("<child />");

    XName parentName = "parent";

    XElement parent1 = new XElement(parentName, child); // Attach.

    object.ReferenceEquals(child, parent1.Elements().Single()).WriteLine(); // True

    object.ReferenceEquals(parentName, parent1.Name).WriteLine(); // True

 

    XElement parent2 = new XElement(parentName, child); // Clone and attach.

    object.ReferenceEquals(child, parent2.Elements().Single()).WriteLine(); // False

    object.ReferenceEquals(parentName, parent2.Name).WriteLine(); // True

 

    XElement element = new XElement("element");

   element.Add(element); // Clone and attach.

    object.ReferenceEquals(element, element.Elements().Single()).WriteLine(); // False

}

Adding, deleting, replacing, updating, and events

Most of APIs to add/replace/delete/update XML structures are very intuitive. And when changing a XObject instance, XObject.Changing and XObject.Changed events are fired before and after the change. For example:

internal static void Manipulate()

{

    XElement child = new XElement("child");

   child.Changing += (sender, e) =>

        $"Before {e.ObjectChange}: ({sender.GetType().Name} {sender}) => {child}".WriteLine();

    child.Changed += (sender, e) =>

        $"After {e.ObjectChange}: ({sender.GetType().Name} {sender}) => {child}".WriteLine();

    XElement parent = new XElement("parent");

   parent.Changing += (sender, e) =>

        $"Before {e.ObjectChange}: ({sender.GetType().Name} {sender}) => {parent.ToString(SaveOptions.DisableFormatting)}".WriteLine();

   parent.Changed += (sender, e) =>

        $"After {e.ObjectChange}: ({sender.GetType().Name} {sender}) => {parent.ToString(SaveOptions.DisableFormatting)}".WriteLine();


    child.Value = "value1";

    // Before Add: (XText value1) => <child />

    // After Add: (XText value1) =>< child>value1</child>

 

    child.Value = "value2";

    // Before Remove: (XText value1) =>< child>value1</child>

    // After Remove: (XText value1) =>< child />

    // Before Add: (XText value2) =>< child />

    // After Add: (XText value2) =>< child>value2</child>

 

    child.Value = string.Empty;

    // Before Remove: (XText value2) =>< child>value2</child>

    // After Remove: (XText value2) =>< child />

    // Before Value: (XElement <child />) => <child />

    // After Value: (XElement< child></child>) => <child></child>

 

    parent.Add(child);

    // Before Add: (XElement <child></child>) =>< parent />

    // After Add: (XElement< child></child>) => < parent><child></child></parent>

 

    child.Add(new XAttribute("attribute", "value"));

    // Before Add: (XAttribute attribute="value") => <child></child>

    // Before Add: (XAttribute attribute="value") => < parent><child></child></parent>

    // After Add: (XAttribute attribute="value") => <child attribute="value"></child>

    // After Add: (XAttribute attribute="value") => <parent><child attribute="value"></child></parent>

 

    child.AddBeforeSelf(0);

    // Before Add: (XText 0) => <parent><child attribute="value"></child></parent>

    // After Add: (XText 0) =>< parent>0<child attribute="value"></child></parent>

 

    parent.ReplaceAll(new XText("Text."));

    // Before Remove: (XText 0) => <parent>0<child attribute="value"></child></parent>

    // After Remove: (XText 0) =>< parent><child attribute="value"></child></parent>

    // Before Remove: (XElement <child attribute="value"></child>) => <parent><child attribute="value"></child></parent>

    // After Remove: (XElement <child attribute="value"></child>) => <parent />

    // Before Add: (XText Text.) =>< parent />

    // After Add: (XText Text.) =>< parent>Text.</parent>

 

    parent.Name = "name";

    // Before Name: (XElement< parent>Text.</parent>) => <parent>Text.</parent>

    // After Name: (XElement< name>Text.</name>) => <name>Text.</name>

 

    XElement clonedChild = new XElement(child);

    clonedChild.SetValue(DateTime.Now); // No tracing.

}

There are many APIs to manipulate XML, but there are only 4 kinds of Changing/Changed events: add object, deleting object, update object value, update element/attribute name. For example, as shown above, the APIs to replace objects are shortcuts of deleting old objects and adding new objects. When setting a string as an element’s value, the element first removes its children if there is any, then add the string as a child text node, if the string is not empty string. Also, an object’s events propagate/bubble up to the ancestors, and children and siblings are not impacted. When an object is cloned, the new object’s events is not observed by the original event handlers.

XElement.SetAttributeValue and XElement.SetElementValue are different from other APIs. They can

·        add a new attribute/child element if it does not exist

·        update the attribute/child element value if it exists:

·        remove the attribute/child element if it exists and the provided value to null.

internal static void SetAttributeValue()

{

    XElement element = new XElement("element");

   element.Changing += (sender, e) =>

        $"Before {e.ObjectChange}: ({sender.GetType().Name} {sender}) => {element}".WriteLine();

   element.Changed += (sender, e) =>

        $"After {e.ObjectChange}: ({sender.GetType().Name} {sender}) => {element}".WriteLine();


    element.SetAttributeValue("attribute", "value1"); // Equivalent to: child1.Add(new XAttribute("attribute", "value1"));

    // Before Add: (XAttribute attribute="value1") => <element />

    // After Add: (XAttribute attribute="value1") => <element attribute="value1" />

 

    element.SetAttributeValue("attribute", "value2"); // Equivalent to: child1.Attribute("attribute").Value = "value2";

    // Before Value: (XAttribute attribute="value1") => <element attribute="value1" />

    // After Value: (XAttribute attribute="value2") => <element attribute="value2" />

 

    element.SetAttributeValue("attribute", null);

    // Before Remove: (XAttribute attribute="value2") => <element attribute="value2" />

    // After Remove: (XAttribute attribute="value2") => <element />

}


internal static void SetElementValue()

{

    XElement parent = new XElement("parent");

   parent.Changing += (sender, e) =>

        $"Before {e.ObjectChange}: {sender} => {parent.ToString(SaveOptions.DisableFormatting)}".WriteLine();

   parent.Changed += (sender, e) =>

        $"After {e.ObjectChange}: {sender} => {parent.ToString(SaveOptions.DisableFormatting)}".WriteLine();


    parent.SetElementValue("child", string.Empty); // Add child element.

    // Before Add: <child></child> => <parent />

    // After Add: <child></child> => <parent><child></child></parent>

 

    parent.SetElementValue("child", "value"); // Update child element.

    // Before Value:< child></child> => <parent><child></child></parent>

    // After Value: <child /> =>< parent><child /></parent>

    // Before Add: value =>< parent><child /></parent>

    // After Add: value => < parent><child>value</child></parent>

 

    parent.SetElementValue("child", null); // Remove child element.

    // Before Remove:< child>value</child> => < parent><child>value</child></parent>

    // After Remove: <child>value</child> => <parent />

}

Annotation

Annotation is not a part of the XML. It is a separate arbitrary data in the memory, and associated with a XObject instance in the memory. The annotation APIs provided by XObject allows adding/querying/deleting any .NET data. Apparently, when cloning or serializing XObject, annotation is ignored on the new XObject and the generated string.

internal static void Annotation()

{

    XElement element = new XElement("element");

    element.AddAnnotation(new Uri("https://microsoft.com"));


    Uri annotation = element.Annotation<Uri>();

   annotation.WriteLine(); // https://microsoft.com

    element.WriteLine(); // <element />

 

    XElement clone = new XElement(element); // element is cloned.

    clone.Annotations<Uri>().Any().WriteLine(); // False

 

    element.RemoveAnnotations<Uri>();

    (element.Annotation<Uri>() == null).WriteLine(); // True

}

Validating XML with XSD

XSD (XML Schema Definition) is the metadata of XML tree, including XML's elements, attributes, constrains rules, etc. System.Xml.Schema.Extensions provides a few APIs to validate XML with provided schema. To obtain a schema, one option is to infer it from existing XML:

public static XmlSchemaSet InferSchema(this XNode source)

{

    XmlSchemaInference schemaInference = new XmlSchemaInference();

    using (XmlReader reader = source.CreateReader())

    {

        return schemaInference.InferSchema(reader);

    }

}

The returned XmlSchemaSet instance contains s sequence of XmlSchema instances, one for each namespace in the source XML. XmlSchema can be converted to XDocument with the help of XmlWriter:

public static XDocument ToXDocument(this XmlSchema source)

{

    XDocument document = new XDocument();

    using (XmlWriter writer = document.CreateWriter())

    {

        source.Write(writer);

    }

    return document;

}

Still take an RSS feed as example, the following code outputs the RSS feed’s schema:

internal static void InferSchemas()

{

    XDocument aspNetRss = XDocument.Load("https://www.flickr.com/services/feeds/photos_public.gne?id=64715861@N07&format=rss2");

    XmlSchemaSet schemaSet = aspNetRss.InferSchema();

    schemaSet.Schemas().Cast<XmlSchema>().WriteLines(schema => schema.ToXDocument().ToString());

}

The printed schema is:

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="rss">

    <xs:complexType>

      <xs:sequence>

        <xs:element name="channel">

          <xs:complexType>

            <xs:sequence>

              <xs:element name="title" type="xs:string" />

              <xs:element name="link" type="xs:string" />

              <xs:element name="description" type="xs:string" />

              <xs:element maxOccurs="unbounded" name="item">

                <xs:complexType>

                  <xs:sequence>

                    <xs:element name="title" type="xs:string" />

                    <xs:element name="link" type="xs:string" />

                    <xs:element name="description" type="xs:string" />

                    <xs:element name="pubDate" type="xs:string" />

                    <xs:element name="guid">

                      <xs:complexType>

                        <xs:simpleContent>

                          <xs:extension base="xs:string">

                            <xs:attribute name="isPermaLink" type="xs:boolean" use="required" />

                          </xs:extension>

                        </xs:simpleContent>

                      </xs:complexType>

                    </xs:element>

                    <xs:element maxOccurs="unbounded" name="category" type="xs:string" />

                  </xs:sequence>

                </xs:complexType>

              </xs:element>

            </xs:sequence>

          </xs:complexType>

        </xs:element>

      </xs:sequence>

      <xs:attribute name="version" type="xs:decimal" use="required" />

    </xs:complexType>

  </xs:element>

</xs:schema>

The data is all gone, and there is only structural description for that RSS feed. Save it to a .xsd file, then it can be visualized in Visual Studio’s XML Schema Explorer:

Now, this RSS feed’s schema, represented by XmlSchemaSet, can be used to validate XML. The following example calls the Validate extension methods for XDocument to validate another RSS feed from Flickr. As demonstrated before, Flickr RSS has more elements. Apparently the validation fails:

internal static void Validate()

{

    XDocument aspNetRss = XDocument.Load("https://weblogs.asp.net/dixin/rss");

    XmlSchemaSet schemaSet = aspNetRss.InferSchema();


    XDocument flickrRss = XDocument.Load("https://www.flickr.com/services/feeds/photos_public.gne?id=64715861@N07&format=rss2");

   flickrRss.Validate(

        schemaSet,

        (sender, args) =>

        {

            $"{args.Severity}: ({sender.GetType().Name}) => {args.Message}".WriteLine();

            // Error: (XElement) => The element 'channel' has invalid child element 'pubDate'. List of possible elements expected: 'item'.

            args.Exception?.WriteLine();

            // XmlSchemaValidationException: The element 'channel' has invalid child element 'pubDate'. List of possible elements expected: 'item'.

        });

}

Validate has another overload accepting a bool parameter addSchemaInfo. When it is called with true for addSchemaInfo, if an element or attribute is validated, the validation details are saved in an IXmlSchemaInfo instance, and associated with this element or attribute as an annotation. Then, the GetSchemaInfo method can be called on each element or attribute, to query that IXmlSchemaInfo annotation, if available. IXmlSchemaInfo can have a lot of information, including a Validity property, intuitively indicating the validation status:

internal static void GetSchemaInfo()

{

    XDocument aspNetRss = XDocument.Load("https://weblogs.asp.net/dixin/rss");

    XmlSchemaSet schemaSet = aspNetRss.InferSchema();


    XDocument flickrRss = XDocument.Load("https://www.flickr.com/services/feeds/photos_public.gne?id=64715861@N07&format=rss2");

    flickrRss.Validate(schemaSet, (sender, args) => { }, addSchemaInfo: true);

    flickrRss

        .Root

        .DescendantsAndSelf()

        .ForEach(element =>

        {

            $"{element.XPath()} - {element.GetSchemaInfo()?.Validity}".WriteLine();

            element.Attributes().WriteLines(attribute =>

                $"{attribute.XPath()} - {attribute.GetSchemaInfo()?.Validity.ToString() ?? "null"}");

        });

    // /rss - Invalid

    // /rss/@version - Valid

    // /rss/@xmlns:media - null

    // /rss/@xmlns:dc - null

    // /rss/@xmlns:creativeCommons - null

    // /rss/@xmlns:flickr - null

    // /rss/channel - Invalid

    // /rss/channel/title - Valid

    // /rss/channel/link - Valid

    // /rss/channel/description - Valid

    // /rss/channel/pubDate - Invalid

    // /rss/channel/lastBuildDate - NotKnown

    // ...

}

Transforming XML with XSL

XSL (Extensible Stylesheet Language) can transform a XML tree to another. XSL transformation can be done with the System.Xml.Xsl.XslCompiledTransform type:

public static XDocument XslTransform(this XNode source, XNode xsl)

{

    XDocument result = new XDocument();

    using (XmlReader sourceReader = source.CreateReader())

    using (XmlReader xslReader = xsl.CreateReader())

    using (XmlWriter resultWriter = result.CreateWriter())

    {

        XslCompiledTransform transform = new XslCompiledTransform();

        transform.Load(xslReader);

        transform.Transform(sourceReader, resultWriter);

        return result;

    }

}

The following example transforms RSS to HTML, the most recent 5 items in RSS are mapped to HTML hyperlinks in an unordered list:

internal static void XslTransform()

{

    XDocument rss = XDocument.Load("https://weblogs.asp.net/dixin/rss");

    XDocument xsl = XDocument.Parse(@"

        <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>

            <xsl:template match='/rss/channel'>

            <ul>

                <xsl:for-each select='item[position() &lt;= 5]'><!--Position is less than or equal to 5.-->

                <li>

                    <a>

                    <xsl:attribute name='href'><xsl:value-of select='link' /></xsl:attribute>

                    <xsl:value-of select='title' />

                    </a>

                </li>

                </xsl:for-each>

            </ul>

            </xsl:template>

        </xsl:stylesheet>");

    XDocument html = rss.XslTransform(xsl);

   html.WriteLine();

    // <ul>

    // <li>

    //   <a href="https://weblogs.asp.net:443/dixin/c-6-0-exception-filter-and-when-keyword">C# 6.0 Exception Filter and when Keyword</a>

    // </li>

    // <li>

    //   <a href="https://weblogs.asp.net:443/dixin/use-fiddler-with-node-js">Use Fiddler with Node.js</a>

    // </li>

    // <li>

    //   <a href="https://weblogs.asp.net:443/dixin/diskpart-problem-cannot-select-partition">DiskPart Problem: Cannot Select Partition</a>

    // </li>

    // <li>

    //   <a href="https://weblogs.asp.net:443/dixin/configure-git-for-visual-studio-2015">Configure Git for Visual Studio 2015</a>

   //  </li>

    // <li>

    //   <a href="https://weblogs.asp.net:443/dixin/query-operating-system-processes-in-c">Query Operating System Processes in C#</a>

    // </li>

    // </ul>

}

The above transformation can also be done with LINQ to Objects/XML query:

internal static void Transform()

{

    XDocument rss = XDocument.Load("https://weblogs.asp.net/dixin/rss");

    XDocument html = rss

        .Element("rss")

        .Element("channel")

        .Elements("item")

        .Take(5)

        .Select(item =>

        {

            string link = (string)item.Element("link");

            string title = (string)item.Element("title");

            return new XElement("li", new XElement("a", new XAttribute("href", link), title));

            // Equivalent to: return XElement.Parse($"<li><a href='{link}'>{title}</a></li>");

        })

        .Aggregate(new XElement("ul"), (ul, li) => { ul.Add(li); return ul; }, ul => new XDocument(ul));

   html.WriteLine();

}

Summary

This chapter discusses how to use LINQ to XML APIs to work with XML data. LINQ to XML provides types to model XML following a declarative paradigm. After loading XML into memory as objects, they can be queried by LINQ to Objects. LINQ to XML provides additional queries for XML, including DOM navigation, ordering, comparison, etc. LINQ to XML also provide APIs for XPath, as well as XML manipulation, annotation, validation, and transformation, etc.

46 Comments

  • Databinding question: DataGridView <=> XDocument (using LINQ-to-XML):

    Learning LINQ has been a lot of fun so far, but despite reading a couple books and a bunch of online resources on the topic, I still feel like a total n00b. Recently, I just learned that if my query returns an Anonymous type, the DataGridView I'm populating will be ReadOnly (because, apparently Anonymous types are ReadOnly.)

    Right now, I'm trying to figure out the easiest way to:

    Get a subset of data from an XML file into a DataGridView,
    Allow the user to edit said data,
    Stick the changed data back into the XML file.
    So far I have Steps 1 and 2 figured out:

    public class Container
    {
    public string Id { get; set; }
    public string Barcode { get; set; }
    public float Quantity { get; set; }
    }

    // For use with the Distinct() operator
    public class ContainerComparer : IEqualityComparer<Container>
    {
    public bool Equals(Container x, Container y)
    {
    return x.Id == y.Id;
    }

    public int GetHashCode(Container obj)
    {
    return obj.Id.GetHashCode();
    }
    }

    var barcodes = (from src in xmldoc.Descendants("Container")
    where src.Descendants().Count() > 0
    select
    new Container
    {
    Id = (string)src.Element("Id"),
    Barcode = (string)src.Element("Barcode"),
    Quantity = float.Parse((string)src.Element("Quantity").Attribute("value"))
    }).Distinct(new ContainerComparer());

    dataGridView1.DataSource = barcodes.ToList();
    This works great at getting the data I want from the XML into the DataGridView so that the user has a way to manipulate the values.

    Upon doing a Step-thru trace of my code, I'm finding that the changes to the values made in DataGridView are not bound to the XDocument object and as such, do not propagate back.

    How do we take care of Step 3? (getting the data back to the XML) Is it possible to Bind the XML directly to the DataGridView? Or do I have to write another LINQ statement to get the data from the DGV back to the XDocument?

    Suggestions?

  • Enjoy exclusive Amazon Originals as well as popular movies and TV shows. Watch anytime, anywhere. Start your free trial

  • thank you so much for a great post.

  • This post is really astounding one! I was delighted to read this, very much useful. Many thanks

  • This article is really fantastic and thanks for sharing the valuable post.

  • awesome post. I’m a normal visitor of your web site and appreciate you taking the time to maintain the nice site. I’ll be a frequent visitor for a long time.

  • Great Article it its really informative and innovative keep us posted with new updates. its was really valuable. 

  • This post is truly inspiring. I like your post and everything you share with us is current and very informative

  • Very interesting topic will bookmark your site to check if you Post more about in the future.

  • Great article Lot’s of information to Read…Great Man Keep Posting and update to People..Thanks

  • https://ma-study.blogspot.com/

  • How fast and how high resolution is the printer? The majority of printers work with high resolution, which has an impressive speed of printing papers per minute. It is important to know the included features of the printer such as Ethernet or Wi-Fi. It is best to determine if it is a monochrome printer or fitted with laser technology, and all-round protection against any threat. One of the most important things to take into consideration is the ability to utilize an ink cartridge for printing .

  • If you are manipulating the "LINQ to XML" language and you have no idea about it, You can get help from this post. Your work will become easy.

    Similarly, we make shopping easy for women, They can buy any desired dresses through our Affordable Women's Clothing in USA service. They will not need to go on any other outlets.

  • 60 days game time is currently the only game time provided by blizzard for gamers, Word of Warcraft. In the past, there were games like 30-day and 180-day, but due to the new policies of this company and the policy that it has considered, the only game time that can be provided for dear gamers is Game Time 60. Is fasting. In the following, we have collected interesting explanations about game time for you, which are worth reading.

    Two months gametime application

    Currently, 2 months gametime is used in all areas of world of warcraft. But if you want to experience a series of exciting and new experiences, you have to buy this game time. These experiences include:
    Use new expansions
    Play in new maps
    Roll up in a new style
    Change in the shape of the game
    Preparing all kinds of game time from Jet Game site
    We are waiting for your support

  • گیم تایم 60 روزه در حال حاضر تنها گیم تایمی است که از طرف کمپانی blizzard برای بازیکنان گیم ، ورد اف وارکرافت ارائه شده است. در گذشته گیم تایم هایی مانند 30 روزه و 180 روزه هم موجود بود اما به دلیل سیاست های جدید این کمپانی و خط مشی که در نظر گرفته است، تنها گیم تایمی که در حال حاضر امکان فراهم کردن آن برای گیمر های عزیز، گیم تایم 60 روزه می باشد. در ادامه توضیحات جالبی در مورد گیم تایم برای شما جمع آوری کرده ایم که خواندنشان خالی از لطف نیست.

    کاربرد گیم تایم دو ماهه

    در حال حاضر گیم تایم 2 ماهه در تمامی زمینه های world of warcraft کاربرد دارد. اما اگر می خواهید که یک سری تجربه های جذاب و جدید را تجربه کنید باید این گیم تایم را خریداری کنید. این تجربه ها عبارتند از:
    استفاده از اکسپنشن های جدید
    بازی در مپ های جدید
    لول آپ به سبک جدید
    تغییر در شکل بازی

  • It's too bad to check your article late. I wonder what it would be if we met a little faster. I want to exchange a little more, but please visit my site Keonhacai and leave a message!!

  • تولید محتوای سریع و فوری = بی کیفیت : از موارید که متاسفانه می تواند باعث بی کیفیت شدن محتوا شود، اسرار کارفرما برای سرعت بخشیدن به تولید محتوای سریع و فوری باشد. ما در سایت کدُ سئو با داشتن بیش از 100 تویسنده فعال و حرفه در کمترین زمان ممکن می توانیم تمامی سفارشات محتوای متنی سایت شما را انجام دهیم.
    انتخاب بهترین سایت تولید محتوا : تنها قیمت کافی نیست! بله کیفیت مهمتر است، قیمت کمتر می تواند به دلیل کیفیت پایین هم باشد پس از آزمون و خطا کردن بپرهیزید و کدُ سئو بهترین سایت تولید محتوا آنلاین را برای انجام خدمات تولید محتوا خود انتخاب نمایید. برای اینکه شما هم یقین داشته باشید ما بهترین خدمات را به شما ارائه خواهیم کرد، نمونه های زیادی را در زمنیه های گوناگون برای شما در قسمت نمونه کارها در همین صفحه آماده نموده ایم.
    محاسبه هزینه تولید محتوا به شرط چاقو : شما پس از سفارش خود می تواند محتوای تولید شده را بررسی نمایید و اگر محتوا مورد رضایت شما نبود آن را برای اصلاح به رد نمایید. ما بدون هیچ گونه هزینه اضافه بهترین را به شما تضمین می دهیم.

  • I was looking for another article by chance and found your article Keo nha cai I am writing on this topic, so I think it will help a lot. I leave my blog address below. Please visit once.

  • I have been looking for articles on these topics for a long time. casinosite I don't know how grateful you are for posting on this topic. Thank you for the numerous articles on this site, I will subscribe to those links in my bookmarks and visit them often. Have a nice day

  • want you to know I am the one who encourages you to keep writing good articles like this.

  • When I read an article on this topic, <a href="https://images.google.com.pe/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">casinosite</a> the first thought was profound and difficult, and I wondered if others could understand.. My site has a discussion board for articles and photos similar to this topic. Could you please visit me when you have time to discuss this topic?

  • Your writing is perfect and complete. <a href="https://images.google.com.pa/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccaratsite</a> However, I think it will be more wonderful if your post includes additional topics that I am thinking of. I have a lot of posts on my site similar to your topic. Would you like to visit once?

  • As I am looking at your writing, <a href="https://images.google.com.om/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">slotsite</a> I regret being unable to do outdoor activities due to Corona 19, and I miss my old daily life. If you also miss the daily life of those days, would you please visit my site once? My site is a site where I post about photos and daily life when I was free.

  • Looking at this article, I miss the time when I didn't wear a mask. <a href="https://images.google.com.np/url?sa=t&url=https%3A%2F%2Fwww.mtclean.blog/">baccaratcommunity</a> Hopefully this corona will end soon. My blog is a blog that mainly posts pictures of daily life before Corona and landscapes at that time. If you want to remember that time again, please visit us.

  • This is what I want from many articles. And I am really interested in your articles. want to let you know.

  • Your site posts an astoundingly splendid article. A responsibility of appreciation is for sharing this sort of post.

  • I quite stumbled upon reading your web journal article, it is really good.

  • Your dedication to delivering high-quality content is evident in every word you write.

  • Thank you for your tireless pursuit of excellence in your writing.

  • really nice post. thanks for sharing beautiful content. I'm pleased that your post has the information I need.

  • Discover the Best Slot Sites to play online. Get welcome offers and bonuses & read the latest slots reviews and demo play before signing up.

  • We provide a safe Toto site. You can use the safety playground and major site.

  • No matter how many times I come to read your articles Never been disappointed It's all fun. You are really good and great. I want to keep reading.

  • This is very good content. I have read and found it useful for me. I like to read this kind of blog.

  • We specialize in offering comprehensive rankings and recommendations for the top 15 private Toto sites in Korea for the first half of 2024. Our goal is to help you find the safest and most reliable options for online betting.

  • Thank you for the clear and concise information. It’s incredibly helpful!

  • Your article is a testament to the depth of your expertise and the breadth of your knowledge.

  • Don't stop at writing articles. Because I will not stop reading your articles. Because it's really good.

  • This is an incredibly great article. Thank you for explaining these details in detail.

  • You can visit our website. The content you mention is really interesting and addictive.

  • It’s a wonderful platform for personal growth.

  • Great article Lot’s of information to Read

  • I gotta most loved this site it appears to be exceptionally useful.

  • Thanks for sharing this useful article.

  • This is very educational content and written well for a change. It’s nice to see that some people still understand how to write a quality post.

  • Nice post! This is a very nice blog that I will definitely come back to more times this year! Thanks for the informative post.

Add a Comment

As it will appear on the website

Not displayed

Your website