tags:

views:

462

answers:

2

Hi I have following XAML code which is the output from XamlWriter.Save():

<StackPanel Name="itemStack" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:mm="clr-namespace:MindManager;assembly=MindManager">
  <mm:Item Width="Auto" Height="Auto">
    <Border BorderThickness="10,10,10,10" Name="border1" Height="Auto">
      <DockPanel>
        <DockPanel LastChildFill="True" Name="dockPanel1" Height="33" DockPanel.Dock="Top">
          <Button Name="deleteItemButton" Width="26" Height="21.638" FlowDirection="LeftToRight" DockPanel.Dock="Right" Grid.IsSharedSizeScope="False">x</Button>
          <TextBox Name="tagsTextBox" Height="19">1</TextBox>
        </DockPanel>
        <TextBox TextWrapping="Wrap" MinLines="5" AcceptsReturn="True" AcceptsTab="True" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled" AutoWordSelection="True" Name="contentTextBox" Width="Auto" MinWidth="600" Height="Auto" MinHeight="80">2</TextBox>
      </DockPanel>
    </Border>
  </mm:Item>
  <mm:Item Width="Auto" Height="Auto">
    <Border BorderThickness="10,10,10,10" Name="border1" Height="Auto">
      <DockPanel>
        <DockPanel LastChildFill="True" Name="dockPanel1" Height="33" DockPanel.Dock="Top">
          <Button Name="deleteItemButton" Width="26" Height="21.638" FlowDirection="LeftToRight" DockPanel.Dock="Right" Grid.IsSharedSizeScope="False">x</Button>
          <TextBox Name="tagsTextBox" Height="19">3</TextBox>
        </DockPanel>
        <TextBox TextWrapping="Wrap" MinLines="5" AcceptsReturn="True" AcceptsTab="True" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Disabled" AutoWordSelection="True" Name="contentTextBox" Width="Auto" MinWidth="600" Height="Auto" MinHeight="80">4</TextBox>
      </DockPanel>
    </Border>
  </mm:Item>
</StackPanel>

I want to convert it to simpler XML with the following XSL:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" xmlns:mm="clr-namespace:MindManager;assembly=MindManager">  
  <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/StackPanel">
      <mindcontainer>
        <xsl:for-each select="mm:Item">
          <minditem>
            <xsl:value-of select="/tagsTextBox"/>
            <xsl:value-of select="/contentTextBox"/>
          </minditem>
        </xsl:for-each>
      </mindcontainer>
    </xsl:template>
</xsl:stylesheet>

I want the output to have the following format:

<mindcontainer>
 <minditem>
   content of tagsTextBox
   content of contentTextBox
 </minditem>
 <minditem>
   content of tagsTextBox
   content of contentTextBox
 </minditem>
</mindcontainer>

But the problem is that all I get from it is:

  x
  1

2







  x
  3

4

One can see that there is only text, there are not text, also the filter doesn't work the x is the label of the close button which I do not want to be outputted.

This is the code Iam using to perform the transform:

    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent=true;

    StringBuilder sb = new StringBuilder();
    using (XmlWriter wr = XmlWriter.Create(sb, settings))
    {
        XamlWriter.Save(scrollViewer1.Content, wr);
    }

    settings.ConformanceLevel = ConformanceLevel.Auto;
    using (XmlReader rd = XmlReader.Create(new StringReader(sb.ToString())))
    {
        XslCompiledTransform trans = new XslCompiledTransform();
        trans.Load("output.xsl");

        trans.Transform(rd, XmlWriter.Create("mindstore.xms", settings));
    }
+3  A: 

In XPath/XSLT 1.0, to select (or match) an element that's in a namespace, you must declare a namespace prefix and use that prefix in your expressions (or patterns). Don't be fooled by the fact that <StackPanel> and other elements don't use prefixes in the input XML. It's using a default namespace, as determined by this default namespace declaration:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

So you need a corresponding declaration in your stylesheet (as you did for the "mm" namespace), except that you need to pick a namespace prefix to use. Any prefix will do (just as you didn't actually have to use "mm").

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  ...>

Then, in your stylesheet, you need to update your expressions and patterns to use the prefix you chose ("x" in my example above):

<xsl:template match="/x:StackPanel">

The reason you're seeing the output you're getting is that your template rule isn't ever getting invoked (because the name doesn't match). Instead, the built-in template rules for each of the XPath node types is getting invoked. For the root node and element nodes, it's to just keep processing children. For text nodes, it's to copy them through. Thus, the behavior you're seeing is the same behavior you'd get with any empty stylesheet (that has no template rules): a dump of all the text nodes in the input document.

It can be a pain to have to prefix every name test in every expression or pattern. That's why XSLT 2.0 introduced a feature called xpath-default-namespace, which makes it possible for XPath expressions and XSLT patterns to use a default namespace, just like elements can in XML. In that case, all you'd have to change is to add one line:

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xpath-default-namespace="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  ...>

But that's only if you you're using an XSLT 2.0 processor. (MSXML, for example, doesn't support XSLT 2.0.)

Before I leave you, I see some more issues in your stylesheet (which you, no doubt, would have discovered next):

        <xsl:value-of select="/tagsTextBox"/>
        <xsl:value-of select="/contentTextBox"/>

Starting an expression with "/" means "start from the document/root node". So these expressions are looking for a <tagsTextBox> document element and a <contentTextBox> document element, respectively. Based on what I see in your sample input document, I think what you meant to write is this:

        <xsl:value-of select=".//x:TextBox[@Name = 'tagsTextBox']"/>
        <xsl:value-of select=".//x:TextBox[@Name = 'contentTextBox']"/>

Hope this helps! Let me know if anything needs further explanation.

Evan Lenz
+1  A: 

You problems are in the XSLT, at the very least:

  1. You need to put your matching expressions in the right namespaces as they are to match. E.g.

    ... xml:xaml='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    <xsl:template match='/xaml:StackPanel'>...
    
  2. The value-of expressions (match /tagsTextBox and /contentText) Boxseem to be intended to match within the StackPanel, but:

    • Also lack namespace qualification
    • Are matching immediate children of the document root, rather than descendent nodes of the current content node. Likely you need:

      <xsl:value-of select='descendent::xaml:TextBox'/>
      

And possibly more.

Richard