Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get LinqToXSD to properly output namespace prefix declarations?

I am experimenting creating XML data binding classes with LinqToXSD and an XML Schema containing a number of imported schemas. All of the schemas are located here.

To accomplish this, I used the following root schema document:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:TmatsG="http://www.spiraltechinc.com/tmats/106-13/TmatsG" elementFormDefault="unqualified">
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon" schemaLocation="TmatsCommonTypes.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsC" schemaLocation="TmatsCGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsD" schemaLocation="TmatsDGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsG" schemaLocation="TmatsGGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsH" schemaLocation="TmatsHGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsM" schemaLocation="TmatsMGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsP" schemaLocation="TmatsPGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsR" schemaLocation="TmatsRGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsS" schemaLocation="TmatsSGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsT" schemaLocation="TmatsTGroup.xsd"/>
    <xs:import namespace="http://www.spiraltechinc.com/tmats/106-13/TmatsV" schemaLocation="TmatsVGroup.xsd"/>
    <xs:element name="Tmats" type="TmatsG:Tmats">
        <xs:annotation>
            <xs:documentation>Tmats Root</xs:documentation>
        </xs:annotation>
    </xs:element>
</xs:schema>

I created classes using Linq to XSD. I then wrote the following test:

[TestMethod()]
public void TmatsXmlExample4()
{
    Tmats tmats = new Tmats
    {
        ProgramName = "My Program",
        OriginationDate = DateTime.Now,
    };
    tmats.PointOfContact.Add(new PointOfContactType
    {
         Address = "12345 Anywhere Street",
         Agency = "My Agency",
         Name = "Robert Harvey",
         Telephone = "111-222-3333"
    });
    Debug.Print(tmats.ToString());
}

I expected output that looked something like this:

<Tmats>
  <TmatsG:ProgramName>My Program</TmatsG:ProgramName>
  <TmatsG:OriginationDate>2012-05-09-07:00</TmatsG:OriginationDate>
  <TmatsG:PointOfContact>
    <TmatsCommon:Name>Robert Harvey</TmatsCommon:Name>
   <TmatsCommon:Agency>My Agency</TmatsCommon:Agency>
    <TmatsCommon:Address>12345 Anywhere Street</TmatsCommon:Address>
    <TmatsCommon:Telephone>111-222-3333</TmatsCommon:Telephone>
  </TmatsG:PointOfContact>
</Tmats>

Instead, what I got was this:

<Tmats>
  <ProgramName xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsG">My Program</ProgramName>
  <OriginationDate xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsG">2012-05-09-07:00</OriginationDate>
  <PointOfContact xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsG">
    <Name xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">Robert Harvey</Name>
    <Agency xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">My Agency</Agency>
    <Address xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">12345 Anywhere Street</Address>
    <Telephone xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">111-222-3333</Telephone>
  </PointOfContact>
</Tmats>

Is there a way to get LinqToXSD to produce the expected output?

like image 946
Robert Harvey Avatar asked May 10 '12 23:05

Robert Harvey


1 Answers

You should map each of the imported schemas:

<?xml version="1.0"?>
    <xs:schema 
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:TmatsCommon="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon"
        xmlns:TmatsC="http://www.spiraltechinc.com/tmats/106-13/TmatsC"
        xmlns:TmatsD="http://www.spiraltechinc.com/tmats/106-13/TmatsD"
        xmlns:TmatsG="http://www.spiraltechinc.com/tmats/106-13/TmatsG"
        xmlns:TmatsH="http://www.spiraltechinc.com/tmats/106-13/TmatsH"
        xmlns:TmatsM="http://www.spiraltechinc.com/tmats/106-13/TmatsM"
        … 
        elementFormDefault="unqualified">

elementFormDefault only applies to the schema that it is in and it doesn't override settings in any include or imports.

If you want to hide namespaces then all schemas must specify elementFormDefault="unqualified". Similarly if you want to expose namespaces every schema must specify elementFormDefault="qualified"

UPDATED after reviewing unit tests:

Your input:

<Tmats 
    xmlns:TmatsG="http://www.spiraltechinc.com/tmats/106-13/TmatsG"
    xmlns:TmatsCommon="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">
    <TmatsG:ProgramName>My Project</TmatsG:ProgramName> 
    <TmatsG:OriginationDate>2012-05-15</TmatsG:OriginationDate> 

Your output:

<Tmats> 
    <Tmats 
        xmlns:TmatsG="http://www.spiraltechinc.com/tmats/106-13/TmatsG"
        xmlns:TmatsCommon="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">
        <TmatsG:ProgramName>My Project</TmatsG:ProgramName> 
        <TmatsG:OriginationDate>2012-05-15</TmatsG:OriginationDate> 

The outstanding problem is the duplication of the tag - everything looks ok to me, still struggling to understand why this is happening.

UPDATE Monday:

I think there is a bug in the LinqToXSD tool - I've run through every combination I can think of and can't consistently work around your problem, however... I have managed to fix the <Tmats> duplication issue:

In your XmlHelper file, change the return statement:

System.Xml.Linq.XDocument xd = System.Xml.Linq.XDocument.Parse(sb.ToString());
return xd.Root.FirstNode.ToString();

I know it's a hack, but it fixes the problem and your LoopbackTest passes.

You don't get any prefixes if you create elements using the Tmats class, I've tried various combinations of attributes and the best I could do was re-attach namespaces. If you are exchanging information with an external system then I do have a fix:

  1. Use your Tmats object in your code,
  2. Serialize it with namespaces,
  3. Run it through an XSLT to map ns to prefixes.

I know it's clunky, but I reckon it's the best you're going to get short of actually fixing the LinqToXSD code.

XSLT to map namespaces to prefixes (you need to maintain the set of namespaces in 'stylesheet' declaration and also in the 'mapper':

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:mapper="http://mapper"
    xmlns:TmatsCommon="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon"
    xmlns:TmatsC="http://www.spiraltechinc.com/tmats/106-13/TmatsC"
    xmlns:TmatsD="http://www.spiraltechinc.com/tmats/106-13/TmatsD"
    xmlns:TmatsG="http://www.spiraltechinc.com/tmats/106-13/TmatsG"
    xmlns:TmatsH="http://www.spiraltechinc.com/tmats/106-13/TmatsH"
    xmlns:TmatsM="http://www.spiraltechinc.com/tmats/106-13/TmatsM"
    xmlns:TmatsP="http://www.spiraltechinc.com/tmats/106-13/TmatsP"
    xmlns:TmatsR="http://www.spiraltechinc.com/tmats/106-13/TmatsR"
    xmlns:TmatsS="http://www.spiraltechinc.com/tmats/106-13/TmatsS"
    xmlns:TmatsT="http://www.spiraltechinc.com/tmats/106-13/TmatsT"
    xmlns:TmatsV="http://www.spiraltechinc.com/tmats/106-13/TmatsV">
  <xsl:output omit-xml-declaration="no" indent="yes"/>
  <xsl:strip-space elements="*"/>
  <mapper:namespaces>
    <ns prefix="TmatsCommon" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon"/>
    <ns prefix="TmatsC" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsC"/>
    <ns prefix="TmatsD" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsD"/>
    <ns prefix="TmatsG" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsG"/>
    <ns prefix="TmatsH" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsH"/>
    <ns prefix="TmatsM" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsM"/>
    <ns prefix="TmatsP" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsP"/>
    <ns prefix="TmatsR" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsR"/>
    <ns prefix="TmatsS" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsS"/>
    <ns prefix="TmatsT" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsT"/>
    <ns prefix="TmatsV" uri="http://www.spiraltechinc.com/tmats/106-13/TmatsV"/>
  </mapper:namespaces>
  <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="*[namespace-uri()=document('')/*/mapper:namespaces/*/@uri]">
  <xsl:variable name="vNS" select="document('')/*/mapper:namespaces/*[@uri=namespace-uri(current())]"/>
  <xsl:element name="{$vNS/@prefix}:{local-name()}" namespace="{namespace-uri()}" >
   <xsl:copy-of select="namespace::*[not(. = namespace-uri(current()))]"/>
   <xsl:copy-of select="@*"/>
   <xsl:apply-templates/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

Produces:

<Tmats>
  <TmatsG:ProgramName xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsG">My Program</TmatsG:ProgramName>
  <TmatsG:OriginationDate xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsG">2012-05-09-07:00</TmatsG:OriginationDate>
  <TmatsG:PointOfContact xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsG">
    <TmatsCommon:Name xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">Robert Harvey</TmatsCommon:Name>
    <TmatsCommon:Agency xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">My Agency</TmatsCommon:Agency>
    <TmatsCommon:Address xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">12345 Anywhere Street</TmatsCommon:Address>
    <TmatsCommon:Telephone xmlns="http://www.spiraltechinc.com/tmats/106-13/TmatsCommon">111-222-3333</TmatsCommon:Telephone>
  </TmatsG:PointOfContact>
</Tmats>

Ok, so this is far from ideal, but your code works fine internally to the project it only when you need to interact with other people that you'd need to fix the xml output (remember to change elementFormDefault="qualified" (or remove it) in your XSD) - if you cache the XSLT as a XslCompiledTransform you'd barely notice it happening at all.

like image 166
web_bod Avatar answered Sep 25 '22 16:09

web_bod