tags:

views:

37

answers:

3

I have a xml-structure thar looks a little like this:

<Page>
 <Id>Page1</Id>
 <Content>
  <Header>This is the Header</Header>
  <Body>This is the body</Body>
  <Nested>
   <NestedItem>
    <Id>N1</Id>
    <Content>This is a nested element</Content>
   </NestedItem>
   <NestedItem>
    <Id>N2</Id>
    <Content>This too is a nested element</Content>
   </NestedItem>
  </Nested>
 </Content>
 <Localizations>
  <Localization>
   <Locale>ES</Locale>
   <Content>
    <Header>Esta un caballo</Header>
    <Body>Esta body</Body>
    <Nested>
     <NestedItem>
      <Id>N2</Id>
      <Content>Esta una element nestado</Content>
     </NestedItem>
    </Nested>
   </Content>
  <Localization>
 </Localizations>
</Page>

and after xslt-transformation, into which i pass the variable "ES", in this case, i want it to look something like this:

<Page>
 <Id>Page1</Id>
 <Content>
  <Header>Esta un caballo</Header>
  <Body>Esta body</Body>
  <Nested>
   <NestedItem>
    <Id>N1</Id>
    <Content>This is a nested element</Content>
   </NestedItem>
   <NestedItem>
    <Id>N2</Id>
    <Content>Esta una element nestado</Content>
   </NestedItem>
  </Nested>
 </Content>
</Page>

ie, i want to translate the elemnts that have corresponding elements in the Localization, but keep the original text where there is orginal text. The thing is that the structure of the content-element might be unknown, so i cant use specific tag-names in the xsl-file.

so far, i've come up whith this:

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:output method="xml" encoding="utf-8" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/&gt;
<xsl:param name="locale"></xsl:param>





<xsl:template match="/" name="foo">

  <xsl:for-each select="./*">
   <xsl:if test="name() != 'Localizations'">
    <xsl:element name="{name()}">

     <xsl:variable name="elementName" select="name()"/>
     <xsl:variable name="elementId" select="Id"/>

     <xsl:variable name="elementContent">
      <xsl:value-of select="./text()" />
     </xsl:variable> 

     <xsl:variable name="localContent">
      <xsl:for-each select="./ancestor::Page[1]/Localizations/Localization">
       <xsl:if test="./Locale = $locale">

          <xsl:copy-of select="*[name()=$elementName]/*"/>

       </xsl:if>
      </xsl:for-each>
     </xsl:variable> 

       <xsl:copy-of select="$localContent"/>
       <xsl:call-template name="foo"/>


    </xsl:element>

   </xsl:if>
  </xsl:for-each>  


</xsl:template>


</xsl:stylesheet>

Which outputs the xml allright, but it creates duplicates of the elements in the content-tag, and only the spanish content, the other tags remain empty. Am I at all going the right way about this? Any pointers would be appriciated, guides to xslt is quite hard to find and i'm trying to teach myself here...

A: 

Here is an XSLT 2.0 stylesheet (you can run with Saxon 9 or AltovaXML Tools or XQSharp)

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

  <xsl:param name="locale" select="'ES'"/>

  <xsl:key name="k1" 
    match="Localizations/Localization[Locale = $locale]//*[Id]" 
    use="Id"/>
  <xsl:key name="k2" 
    match="Localizations/Localization[Locale = $locale]//*[not(Id) and not(*)]" 
    use="local-name()"/>

  <xsl:template match="Page/Content//*[not(Id) and *]">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Page">
    <xsl:copy>
      <xsl:copy-of select="Id"/>
      <xsl:apply-templates select="Content"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Page//*[Id]">
    <xsl:variable name="loc" select="key('k1', Id)"/>
    <xsl:choose>
      <xsl:when test="$loc">
        <xsl:copy-of select="$loc"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:copy-of select="."/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="Page//*[not(Id) and not(*)]">
    <xsl:variable name="loc" select="key('k2', local-name())"/>
    <xsl:choose>
      <xsl:when test="$loc">
        <xsl:copy-of select="$loc"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:copy>
          <xsl:apply-templates/>
        </xsl:copy>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

But I am not sure your requirements are clearly stated and implemented by that stylesheet.

Martin Honnen
+1  A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kContentByElName" match="Localization/Content/*"
  use="name()"/>

 <xsl:key name="kNestedById" match="Localization/Content/Nested/NestedItem"
  use="Id"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match="/*/Content/*[not(self::Nested)]/text()">
   <xsl:variable name="vTranslation"
        select="key('kContentByElName', name(..))"/>
   <xsl:value-of select="$vTranslation | self::node()[not($vTranslation)]"/>
 </xsl:template>

 <xsl:template match=
   "NestedItem[not(ancestor::Localization)]/Content/text()">
   <xsl:variable name="vTranslation"
        select="key('kNestedById', ../../Id)/Content"/>
   <xsl:value-of select="$vTranslation | self::node()[not($vTranslation)]"/>
 </xsl:template>

 <xsl:template match="Localizations"/>
</xsl:stylesheet>

when applied on the provided XML document:

<Page>
 <Id>Page1</Id>
 <Content>
  <Header>This is the Header</Header>
  <Body>This is the body</Body>
  <Nested>
   <NestedItem>
    <Id>N1</Id>
    <Content>This is a nested element</Content>
   </NestedItem>
   <NestedItem>
    <Id>N2</Id>
    <Content>This too is a nested element</Content>
   </NestedItem>
  </Nested>
 </Content>
 <Localizations>
  <Localization>
   <Locale>ES</Locale>
   <Content>
    <Header>Esta un caballo</Header>
    <Body>Esta body</Body>
    <Nested>
     <NestedItem>
      <Id>N2</Id>
      <Content>Esta una element nestado</Content>
     </NestedItem>
    </Nested>
   </Content>
  </Localization>
 </Localizations>
</Page>

produces the wanted, correct result:

<Page>
   <Id>Page1</Id>
   <Content>
      <Header>Esta un caballo</Header>
      <Body>Esta body</Body>
      <Nested>
         <NestedItem>
            <Id>N1</Id>
            <Content>This is a nested element</Content>
         </NestedItem>
         <NestedItem>
            <Id>N2</Id>
            <Content>Esta una element nestado</Content>
         </NestedItem>
      </Nested>
   </Content>
</Page>

Do note:

  1. This is a pure XSLT 1.0 solution.

  2. Keys are used for fast searching.

  3. All nodes are copied "as-is" by using the identity rule.

  4. Templates matching the nodes that need processing override the identity rule.

  5. Whenever there is no translation found the original text is preserved.

Dimitre Novatchev
Good solution (+1), one bug though. The variable `vTranslation` should be `<xsl:variable name="vTranslation" select="key('kNestedById', ../../Id)/Content"/>`. Currently the Id is concatenated with the translated text (extra "N2" in your result `<Content>N2Esta una element nestado</Content>`)
jasso
@jasso: Thanks for this observation. Fixed now.
Dimitre Novatchev
+1  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:param name="pLang" select="'ES'"/>
    <xsl:key name="kElementByLang"
             match="Localization/Content/*[not(self::Nested)]|
                    Localization/Content/Nested/NestedItem"
             use="concat(ancestor::Localization/Locale,'++',
                         name(),'++',Id)"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="Localizations"/>
    <xsl:template match="Page/Content//*">
        <xsl:variable name="vMatch"
                      select="key('kElementByLang',
                                  concat($pLang,'++',
                                         name(),'++',
                                         Id))"/>
        <xsl:apply-templates select="$vMatch"/>
        <xsl:if test="not($vMatch)">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

Output:

<Page>
    <Id>Page1</Id>
    <Content>
        <Header>Esta un caballo</Header>
        <Body>Esta body</Body>
        <Nested>
            <NestedItem>
                <Id>N1</Id>
                <Content>This is a nested element</Content>
            </NestedItem>
            <NestedItem>
                <Id>N2</Id>
                <Content>Esta una element nestado</Content>
            </NestedItem>
        </Nested>
    </Content>
</Page>
Alejandro