views:

51

answers:

1

I'm currently trying to find a way of adding some extra elements to existing xml documents in schema order.

The xml has a schema attached to it but I've been unable to find a way to get XDocument manipulations to conform to the schema order.

Example schema extract

<xs:element name="control" minOccurs="1" maxOccurs="1">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="applicationId" type="xs:unsignedByte" minOccurs="1" maxOccurs="1" />
      <xs:element name="originalProcId" type="xs:unsignedByte" minOccurs="0" maxOccurs="1" />
      <xs:element name="dateCreated" type="xs:date" minOccurs="0" maxOccurs="1" />
      <xs:element name="requestId" type="xs:string" minOccurs="0" maxOccurs="1" />
    </xs:sequence>
  </xs:complexType>
</xs:element>

Example xml input

<?xml version="1.0" encoding="utf-8" ?>
<someDocument xmlns="urn:Test">
  <control>
    <applicationId>19</applicationId>
    <dateCreated>2010-09-18</dateCreated>
  </control>
  <body />
</someDocument>

Example code segment

XDocument requestDoc = XDocument.Load("control.xml");

XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("urn:MarksTest", XmlReader.Create("body.xsd"));

// Valid according to the schema
requestDoc.Validate(schemas, null, true);

XElement controlBlock = requestDoc.Descendants("control").First();

controlBlock.Add(new XElement("originalProcId", "2"));
controlBlock.Add(new XElement("requestId", "TestRequestId"));

// Not so valid
controlBlock.Validate(controlBlock.GetSchemaInfo().SchemaElement, schemas, null, true);

I need to add the OriginalProcId and requestId elements, but they need to go to specific locations in the Control element, not just as last children, Of course its not as easy as doing an AddAfterSelf on the previous element as I have just shy of 100 optional elements that may or may not be in the xml.

I've tried using the Validate method to embed the schema validation info set into the XDocument and I thought that might do the trick but it didn't seem to have an effect on the element insertion location.

Is there a way to do this or am I out of luck?

A: 

I ended up doing this with some xlst that transformed the node to itself but ordered, here is the code

// Create a reader for the existing control block
var controlReader = controlBlock.CreateReader();

// Create the writer for the new control block
XmlWriterSettings settings = new XmlWriterSettings {
    ConformanceLevel = ConformanceLevel.Fragment, Indent = false, OmitXmlDeclaration = false };

StringBuilder sb = new StringBuilder();
var xw = XmlWriter.Create(sb, settings);

// Load the style sheet from the assembly
Stream transform = Assembly.GetExecutingAssembly().GetManifestResourceStream("ControlBlock.xslt");
XmlReader xr = XmlReader.Create(transform);

// Load the style sheet into the XslCompiledTransform
XslCompiledTransform xslt = new XslCompiledTransform();
xslt.Load(xr);

// Execute the transform
xslt.Transform(controlReader, xw);

// Swap the old control element for the new one
controlBlock.ReplaceWith(XElement.Parse(sb.ToString()));
squig