Not being an XML expert I'm struggling with this:
I need to populate an ASP.Net Treeview control from an XML dataSource. I can usually do this no problem except on this occasion the data I've been provided with is in this format:
<Staff>
<ID>1</ID>
<Name>Boss 1</Name>
<JobTitle>Top Boss</JobTitle>
<Staff>
<ID>2</ID>
<Name>Boss 2</Name>
<JobTitle>2nd Top Boss</JobTitle>
<Staff>
<ID>3</ID>
<Name>Boss 3</Name>
<JobTitle>3rd Top Boss</JobTitle>
<Staff>
<ID>4</ID>
<Name>Worker 1</Name>
<JobTitle>Worker</JobTitle>
</Staff>
</Staff>
</Staff>
</Staff>
and I need it to be like this for the treeview control:
<Staff ID="1" Name="Boss 1" JobTitle="Top Boss">
<Staff ID="2" Name="Boss 2" JobTitle="2nd Top Boss" >
<Staff ID="3" Name="Boss 3" JobTitle="3rd Top Boss" >
<Staff ID="4" Name="Worker 1" JobTitle="Worker" ></Staff>
</Staff>
</Staff>
</Staff>
I know this should be a doddle to do with an xsl file, but I'm failing miserably. My last attempt was:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Staff">
<Staff>
<xsl:apply-templates/>
</Staff>
</xsl:template>
<xsl:template match="Staff">
<Staff>
<xsl:for-each select="*">
<xsl:attribute name="{name()}">
<xsl:value-of select="text()"/>
</xsl:attribute>
</xsl:for-each>
</Staff>
</xsl:template>
</xsl:stylesheet>
Any ideas?
Predictably I figured it out 30mins after posting. The following converts the xml above to the required format and appears to transform any xml elements to attibutes.
<?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">
<xsl:template match="*">
<xsl:copy>
<xsl:for-each select="#*|*[not(* or #*)]">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
<xsl:apply-templates select="*[* or #*]|text()"/>
</xsl:copy>
</xsl:template>
Related
I have an XML file that I haven't been able to get into a good data.frame format. I'm close but it's not quite there yet.
cellosaurus.xml slightly modified this file by removing everything before and after <cell-line-list> and </cell-line-list> tags
This is the messy code I've written so far:
require(XML)
require(xml2)
require(rvest)
require(dplyr)
require(xmltools)
require(stringi)
require(gtools)
setwd("~/Documents/Cancer_Cell_Lines/Cellosaurus")
file <- "cellosaurus.xml"
cellosaurus <- file %>% xml2::read_xml()
nodeset <- cellosaurus %>% xml_children()
terminal_xpaths <- nodeset[1] %>% xml_get_paths() %>% unlist() %>% unique()
terminal_nodesets <- lapply(terminal_xpaths[1], xml2::xml_find_all, x = cellosaurus)
df_list <- terminal_nodesets %>% purrr::map(xml_dig_df)
df <- lapply(df_list[[1]], function(x) as.data.frame(x))
table <- do.call("smartbind", df)
Problem 1: There are duplicate column names that are mixed up. For example in the file there are many paths that end up at a node called cv.term like
"/cell-line-list/cell-line/disease-list/cv-term"
"/cell-line-list/cell-line/species-list/cv-term"
"/cell-line-list/cell-line/derived-from/cv-term"
but in the table I get columns called cv.term, cv.term.1,cv.term.2 but the contents are mixed up because of missing data. Is there a way to fix this.
Problem 2: The file is big and it takes a long time to run (I've only been able to test on a small subset of the full file), I haven't been able to figure out how to split the xml correctly except by splitting into as many files are there are nodes ~109,000. And then I had a hard time incorporating that many files into my code for R to read.
Any help appreciated.
To use the relational database terminology, consider data normalization. Specifically, keep your data long as most nodes in XML are practically all one-to-many lists which you can extract each one as individual long data frames and merge together by a unique id such as cell_line node number.
Fortunately, there is a great extraction tool available known as XSLT, the special purpose, declarative language (same type as SQL) designed to transform XML into various end use needs such as extracting the individual pieces that you can parse more simply into data frames and then merge all items together. The beauty too is XSLT has nothing to do with R and is portable to other application layers (Java, PHP, Python) or dedicated XSLT processors.
See process below for roadmap to final solution. All XSLT scripts below parses from a specific part of every cell-line node and flattens XML to one child level:
R
library(xml2)
library(xslt) # INSTALL PACKAGE BEFORE HAND
library(dplyr) # ONLY FOR bind_rows
# PARSE XML AND XSLT
doc <- read_xml('Cellosaurus.xml')
scripts <- list.files(path='/path/to/xslt/scripts', pattern='.xsl')
xpaths <- c('//accession', '//cell-line', '//hla_gene', '//marker',
'//name', '//species_list', '//url')
proc_xml_parse <- function(x, s) {
style <- read_xml(s, package = "xslt")
# TRANSFORM INPUT INTO OUTPUT
new_xml <- xslt::xml_xslt(doc, style)
# INNER DF LIST BUILD
df_list <- lapply(xml_find_all(new_xml, x), function(x) {
vals <- xml_children(x)
setNames(data.frame(t(xml_text(vals)), stringsAsFactors = FALSE), xml_name(vals))
})
bind_rows(df_list)
}
# OUTER DF LIST BUILD
df_list <- Map(proc_xml_parse, xpaths, scripts)
# CHAIN MERGE
final_df <- Reduce(function(x,y) merge(x, y, by="cell_num", all=TRUE), df_list)
XSLT Scripts
Save each as separate .xsl or .xslt files (special .xml files) to be loaded in R above. Add more XSLT scripts by replicating patterns for other list nodes in XML as below does not capture all.
Cell Line List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:copy>
<cell_num>
<xsl:value-of select="count(preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Accession List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:apply-templates select="accession-list"/>
</xsl:template>
<xsl:template match="accession-list">
<xsl:apply-templates select="accession"/>
</xsl:template>
<xsl:template match="accession">
<xsl:copy>
<cell_num>
<xsl:value-of select="count(ancestor::cell-line[1]/preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<accession_value><xsl:value-of select="."/></accession_value>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Name List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:apply-templates select="name-list"/>
</xsl:template>
<xsl:template match="name-list">
<xsl:apply-templates select="name"/>
</xsl:template>
<xsl:template match="name">
<xsl:copy>
<cell_num>
<xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<name_value><xsl:value-of select="."/></name_value>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Web Page List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:apply-templates select="web-page-list"/>
</xsl:template>
<xsl:template match="web-page-list">
<xsl:apply-templates select="url"/>
</xsl:template>
<xsl:template match="url">
<xsl:copy>
<cell_num>
<xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<url_value><xsl:value-of select="."/></url_value>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
HLA List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:apply-templates select="hla-lists/hla-list"/>
</xsl:template>
<xsl:template match="hla-list">
<xsl:apply-templates select="hla-gene"/>
</xsl:template>
<xsl:template match="hla-gene">
<hla_gene>
<cell_num>
<xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<hla_value><xsl:value-of select="."/></hla_value>
</hla_gene>
</xsl:template>
</xsl:stylesheet>
Special List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:apply-templates select="species-list/cv-term"/>
</xsl:template>
<xsl:template match="cv-term">
<species_list>
<cell_num>
<xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<species_value><xsl:value-of select="."/></species_value>
</species_list>
</xsl:template>
</xsl:stylesheet>
Marker List
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Cellosaurus">
<xsl:copy>
<xsl:apply-templates select="cell-line-list/cell-line"/>
</xsl:copy>
</xsl:template>
<xsl:template match="cell-line">
<xsl:apply-templates select="str-list"/>
</xsl:template>
<xsl:template match="str-list">
<xsl:apply-templates select="marker-list"/>
</xsl:template>
<xsl:template match="marker-list">
<xsl:apply-templates select="marker"/>
</xsl:template>
<xsl:template match="marker">
<xsl:copy>
<cell_num>
<xsl:value-of select="count(ancestor::cell-line/preceding-sibling::*)+1"/>
</cell_num>
<xsl:for-each select="#*">
<xsl:element name="{name(.)}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<xsl:copy-of select="marker-data-list/marker-data/alleles"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output
After chain merge where values repeat for every unique row similar to SQL joins for long data frames (many-to-many). Do note: there is a named list of data frames should you not want below merged output:
Just one comment: when you say "~109,000 cell lines with variations in missing data between each cell-line", you need to understand that the only madatory fields in a Cellosaurus entry are the primary accession, the cell line name (identifier), the cell line category and the taxonomy, all the rest are not required. All this is described in the cellosaurus.xsd files either using "minoccurs="0" or use "optional" depending on the type of field.
My Input XML is having Header, Content and Footer Part. Conversion from XML to JSON works well using XSLT. But I need the output as a three parts as header, Content and Footer:
My Input XML file is:
<header>
<trackingSettings>
<urlcode>W3333</urlcode>
<apiurl>http://mlucenter.com/like/api</apiurl>
</trackingSettings>
</header>
<mlu3_body>
<columnsCount>2</columnsCount>
<lineBackground>linear-gradient(to right, rgba(94, 172, 192, 0) 0%, c4cccf 50%, rgba(94, 172, 192, 0) 100%)</lineBackground>
</mlu3_body>
<footer>
<buttons>
<button/>
</buttons>
<banner/>
</footer>
My XSLT using:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" method="xml" />
<xsl:template match="*">
<xsl:value-of select="name()"/> : <xsl:call-template name="Properties"/>
</xsl:template>
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="Properties"/>
</xsl:template>
<xsl:template name="Properties">
<xsl:variable name="childName" select="name(*[1])"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"<xsl:value-of select="."/>"</xsl:when>
<xsl:when test="count(*[name()=$childName]) > 1">{ "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] }</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>
<xsl:template match="#*">"<xsl:value-of select="name()"/>" : '<xsl:value-of select="."/>',
</xsl:template>
</xsl:stylesheet>
Here im using Saxon PE in the oxygen:
I want this XML converted to 3 JSON files named header.json, content.json(mlu3_body) and footer.json in the output.
Is this possible by using XSLT or do I want to keep all input files separately. Please provide some ideas.
Change the XSLT to
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="*">
<xsl:value-of select="name()"/> : <xsl:call-template name="Properties"/>
</xsl:template>
<xsl:template match="*" mode="ArrayElement">
<xsl:call-template name="Properties"/>
</xsl:template>
<xsl:template name="Properties">
<xsl:variable name="childName" select="name(*[1])"/>
<xsl:choose>
<xsl:when test="not(*|#*)">"<xsl:value-of select="."/>"</xsl:when>
<xsl:when test="count(*[name()=$childName]) > 1">{ "<xsl:value-of select="$childName"/>" :[<xsl:apply-templates select="*" mode="ArrayElement"/>] }</xsl:when>
<xsl:otherwise>{
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
}</xsl:otherwise>
</xsl:choose>
<xsl:if test="following-sibling::*">,</xsl:if>
</xsl:template>
<xsl:template match="#*">"<xsl:value-of select="name()"/>" : '<xsl:value-of select="."/>',
</xsl:template>
<xsl:template match="header">
<xsl:result-document href="header.json">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
<xsl:template match="mlu3_body">
<xsl:result-document href="content.json">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
<xsl:template match="footer">
<xsl:result-document href="footer.json">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
and it should generate three result files for those three elements. You will have to edit your question and tell us exactly which result you want if the produced contents is not quite right yet. It is also not clear whether the snippet of XML you have shown is part of a larger document.
I have the following XML data:
<result>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RatePlanID>1</RatePlanID>
<RoomRatePlan>Advance</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<Rate>79.00</Rate>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</row>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RatePlanID>2</RatePlanID>
<RoomRatePlan>Standard</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<Rate>89.00</Rate>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</row>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>2</RoomID>
<RoomName>Double Room</RoomName>
<RatePlanID>1</RatePlanID>
<RoomRatePlan>Advance</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>2</Max_Person>
<RackRate>199</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<Rate>89.00</Rate>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</row>
</result>
My XSLT for the above xml is this:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" method="xml" />
<xsl:strip-space elements="*"/>
<!-- Default template : ignore unrecognized elements and text -->
<xsl:template match="*|text()" />
<!-- Match document root : add hotels element and process each children node of result -->
<xsl:template match="/">
<hotels>
<!-- We assume that the XML documents are always going to follow the structure:
result as the root node and xml_acc elements as its children -->
<xsl:for-each select="result/row">
<result>
<hotel_rooms>
<xsl:element name="hotel_id">
<xsl:value-of select="AccommodationID"/>
</xsl:element>
<xsl:apply-templates />
</hotel_rooms>
<xsl:element name="Rate">
<xsl:element name="RoomRatePlan">
<xsl:value-of select="RoomRatePlan"/>
</xsl:element>
<xsl:element name="numeric_price">
<xsl:value-of select="Rate"/>
</xsl:element>
</xsl:element>
</result>
</xsl:for-each>
</hotels>
</xsl:template>
<!-- Elements to be copied as they are -->
<xsl:template match="NoOfNights|RoomName|RoomSize|Max_Person|RackRate|RatePerDay|CurrencySymbol|NoOfRoomsAvailable|RoomDescription|RoomFacilities|PolicyComments|Breakfast|Policy|Message">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="Photo_Max60">
<RoomImages>
<Photo_Max60>
<xsl:value-of select="." />
</Photo_Max60>
<Photo_Max300>
<xsl:value-of select="../Photo_Max300" />
</Photo_Max300>
<Photo_Max500>
<xsl:value-of select="../Photo_Max500" />
</Photo_Max500>
</RoomImages>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0, I want to group by Room ID and Hotel ID . so in the above data, I Want the result like this.
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</result>
<hotels>
I want a xslt file for the above xml output i need.please help..
Muenchian method:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:key name="room-per-hotel" match="result" use="concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID)" />
<xsl:template match="/">
<hotels>
<xsl:for-each select="hotels/result[count(. | key('room-per-hotel', concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID))[1]) = 1]">
<xsl:sort select="concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID)" />
<result>
<xsl:copy-of select="hotel_rooms"/>
<Rate>
<xsl:copy-of select="key('room-per-hotel', concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID))/Rate/*"/>
</Rate>
</result>
</xsl:for-each>
</hotels>
</xsl:template>
</xsl:stylesheet>
Working example
Well neither your input nor your wanted result is well-formed with tags not being closed properly and the existence of an entity reference £ to a not declared entity but if you want to group with XSLT 1.0 then have a look at the following XSLT 1.0 sample using Muenchian grouping:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="group" match="result" use="concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID)"/>
<xsl:template match="hotels">
<xsl:copy>
<xsl:apply-templates select="result[generate-id() = generate-id(key('group', concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID))[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="result">
<xsl:copy>
<xsl:copy-of select="hotel_rooms"/>
<Rate>
<xsl:copy-of select="key('group', concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID))/Rate/*"/>
</Rate>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It transforms
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
</Rate>
</result>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</hotels>
into
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize />
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</hotels>
I'm having trouble publishing an RSS feed from my Umbraco site. I found this Umbraco.TV video and tried to follow the instructions there using an XSLT selector to select all nodes of a give type, like so:
umbraco.library.GetXmlAll()/node [#nodeTypeAlias='Alias]/node
As sugested here but that didn't work. Apparently the schema has changed or something. When this didn't work I looked for a plugin to do this kind of stuff and was amazed to find just 2 plugins, both of them with little-to-no documentation and neither seemed to work (first plugin, second plugin).
So once and for all, I'd like to have a definite answer - how does one publish an RSS feed in Umbraco?
Here's an XSLT that we use for News Items RSS (News Items are under a News Page). Let me know if that helps. I also have versions for Blogs.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rssdatehelper="urn:rssdatehelper"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
xmlns:umbraco.library="urn:umbraco.library" xmlns:Exslt.ExsltCommon="urn:Exslt.ExsltCommon" xmlns:Exslt.ExsltDatesAndTimes="urn:Exslt.ExsltDatesAndTimes" xmlns:Exslt.ExsltMath="urn:Exslt.ExsltMath" xmlns:Exslt.ExsltRegularExpressions="urn:Exslt.ExsltRegularExpressions" xmlns:Exslt.ExsltStrings="urn:Exslt.ExsltStrings" xmlns:Exslt.ExsltSets="urn:Exslt.ExsltSets"
exclude-result-prefixes="msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings Exslt.ExsltSets ">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:param name="currentPage"/>
<!-- Update these variables to modify the feed -->
<xsl:variable name="RSSNoItems" select="/macro/RSSNoItems"/>
<xsl:variable name="RSSTitle" select="/macro/RSSTitle"/>
<xsl:variable name="SiteURL" select="concat('http://',umbraco.library:RequestServerVariables('HTTP_HOST'))"/>
<xsl:variable name="RSSDescription" select="/macro/RSSDescription"/>
<xsl:variable name="source" select="/macro/source"/>
<!-- This gets all news and events and orders by updateDate to use for the pubDate in RSS feed -->
<xsl:variable name="pubDate">
<xsl:for-each select="umbraco.library:GetXmlNodeById($source)/* [#isDoc and string(umbracoNaviHide) != '1']">
<xsl:sort select="./newsDate" order="descending" />
<xsl:if test="position() = 1">
<xsl:value-of select="./newsDate" />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<!-- change the mimetype for the current page to xml -->
<xsl:value-of select="umbraco.library:ChangeContentType('text/xml')"/>
<xsl:text disable-output-escaping="yes"><?xml version="1.0" encoding="UTF-8"?></xsl:text>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
>
<channel>
<title>
<xsl:value-of select="$RSSTitle"/>
</title>
<link>
<xsl:value-of select="$SiteURL"/>
</link>
<pubDate>
<xsl:value-of select="$pubDate"/>
</pubDate>
<generator>umbraco v4</generator>
<description>
<xsl:value-of select="$RSSDescription"/>
</description>
<language>en</language>
<xsl:for-each select="umbraco.library:GetXmlNodeById($source)/* [#isDoc and string(umbracoNaviHide) != '1']">
<xsl:sort select="./newsDate" order="descending" />
<xsl:if test="position() <= $RSSNoItems">
<xsl:call-template name="RSSitem">
<xsl:with-param name="node" select="current()"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</channel>
</rss>
</xsl:template>
<xsl:template match="node">
<xsl:if test="position() <= $RSSNoItems">
<item>
<title>
<xsl:value-of select="#nodeName"/>
</title>
<link>
<xsl:value-of select="$SiteURL"/>
<xsl:value-of select="umbraco.library:NiceUrl(#id)"/>
</link>
<pubDate>
<xsl:value-of select="umbraco.library:FormatDateTime(./newsDate,'r')" />
</pubDate>
<guid>
<xsl:value-of select="$SiteURL"/>
<xsl:value-of select="umbraco.library:NiceUrl(#id)"/>
</guid>
<content:encoded>
<xsl:value-of select="concat('<![CDATA[ ', ./bodyText,']]>')" disable-output-escaping="yes"/>
</content:encoded>
</item>
</xsl:if>
</xsl:template>
<xsl:template name="RSSitem">
<xsl:param name="node"/>
<item>
<title>
<xsl:value-of select="$node/#nodeName"/>
</title>
<link>
<xsl:value-of select="$SiteURL"/><xsl:value-of select="umbraco.library:NiceUrl($node/#id)"/>
</link>
<pubDate>
<xsl:value-of select="umbraco.library:FormatDateTime(./newsDate,'r')"/>
</pubDate>
<dc:creator><xsl:value-of select="#writerName"/></dc:creator>
<xsl:for-each select="umbraco.library:Split($node/categories, ',')/value">
<xsl:sort data-type="text" order="ascending"/>
<category>
<xsl:value-of select="current()"/>
</category>
</xsl:for-each>
<guid>
<xsl:value-of select="$SiteURL"/><xsl:value-of select="umbraco.library:NiceUrl($node/#id)"/>
</guid>
<description>
<xsl:value-of select="concat('<![CDATA[ ', $node/summary,']]>')" disable-output-escaping="yes"/>
</description>
<content:encoded>
<xsl:value-of select="concat('<![CDATA[ ', $node/bodyText,']]>')" disable-output-escaping="yes"/>
</content:encoded>
</item>
</xsl:template>
</xsl:stylesheet>
I got everything working (thank empo) except the ctrlname column. I don't know the syntax well enough. What I am trying to do is use the xslt to sort the xml in the gridview by the column name. Everything is working but the ctrlname column. How do I pass an attribute to the XSLT? I've tried: #name, Data/#name, Data[#name], ctrlname. Nothing works.
XmlDataSource1.EnableCaching = False
Dim xslTrnsform As System.Xml.Xsl.XsltArgumentList = New System.Xml.Xsl.XsltArgumentList
xslTrnsform.AddParam("sortby", "", sortAttr)
xslTrnsform.AddParam("orderas", "", orderby)
XmlDataSource1.TransformArgumentList = xslTrnsform
XmlDataSource1.DataFile = "~/App_LocalResources/DST_Test.xml"
XmlDataSource1.XPath = "//data"
XmlDataSource1.TransformFile = xsltFileName
'XmlDataSource1.DataBind()
GridView1.DataSource = XmlDataSource1
GridView1.DataBind()
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">
<xsl:param name="sortby"></xsl:param>
<xsl:param name="orderas"></xsl:param>
<xsl:output method="xml" indent="yes"/>
<!--<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>-->
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:attribute name="ctrlname">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value" />
</xsl:attribute>
<xsl:attribute name="comment">
<xsl:value-of select="comment" />
</xsl:attribute>
</data>
</xsl:template>
</xsl:stylesheet>
XML input
<?xml version="1.0" encoding="utf-8" ?>
<root>
<data name="Test1.Text" xml:space="preserve">
<value>Please Pick Bare Pump</value>
<comment>Tab - Pump Configuration</comment>
</data>
<data name="Test2.Text" xml:space="preserve">
<value>Complete</value>
<comment>A07</comment>
</data>
<data name="Test3.Text" xml:space="preserve">
<value>Confirmed</value>
<comment>A01</comment>
</data>
</root>
The currently accepted answer has one flaw: Whenever there is an attribute of data with the same name as a child element of data, the sort will always be performed using as keys the values of the attribute. Also, it is too long.
This solution solves the problem (and is shorter) allowing to specify whether the sort should be by attribute-name or by element-name:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="sortby" select="'attrib!name'"/>
<xsl:param name="orderas" select="'ascending'"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select=
"*[name()=substring-after($sortby, 'elem!')]
|
#*[name()=substring-after($sortby, 'attrib!')]"
data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data ctrlname="{#name}" value="{value}"
comment="{comment}"/>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document (based on the provided one, but made a little-bit more interesting):
<root>
<data name="Test3.Text" xml:space="preserve">
<value>Please Pick Bare Pump</value>
<comment>Tab - Pump Configuration</comment>
<name>X</name>
</data>
<data name="Test2.Text" xml:space="preserve">
<value>Complete</value>
<comment>A07</comment>
<name>Z</name>
</data>
<data name="Test1.Text" xml:space="preserve">
<value>Confirmed</value>
<comment>A01</comment>
<name>Y</name>
</data>
</root>
the correct result (sorted by the name attribute) is produced:
<root>
<data ctrlname="Test1.Text" value="Confirmed" comment="A01"/>
<data ctrlname="Test2.Text" value="Complete" comment="A07"/>
<data ctrlname="Test3.Text" value="Please Pick Bare Pump" comment="Tab - Pump Configuration"/>
</root>
Now, replace the <xsl:param name="sortby" select="'attrib!name'"/> with:
<xsl:param name="sortby" select="'elem!name'"/>
and apply the transformation again on the same XML document. This time we get the result correctly sorted by the values of the child-element name:
<root>
<data ctrlname="Test3.Text" value="Please Pick Bare Pump" comment="Tab - Pump Configuration"/>
<data ctrlname="Test1.Text" value="Confirmed" comment="A01"/>
<data ctrlname="Test2.Text" value="Complete" comment="A07"/>
</root>
Explanation:
To distinguish whether we want to sort by an element-child or by an attribute, we use the convention that elem!someName means the sort must be by the values of a child element named someName. Similarly, attrib!someName means the sort must be by the values of an attribute named someName.
The <xsl:sort> insruction is modified accordingly to select as key correctly either an attribute or a child element. No ambiguity is allowed, because the starting substring of the sortby parameter now uniquely identifies whether the key should be an attribute or a child element.
Yes, I'm sorry didnt notice that you wanted also sort by attributes. Note also that you have changed the syntax of xsl:param and it's not correct in that way. It's very important that you keep the single quotes inside the double ones. Here is the final template:
<?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">
<xsl:param name="sortby" select="'value'"/>
<xsl:param name="orderas" select="'ascending'"/>
<xsl:output method="xml" indent="yes"/>
<!--<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>-->
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby]|#*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:attribute name="ctrlname">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value" />
</xsl:attribute>
<xsl:attribute name="comment">
<xsl:value-of select="comment" />
</xsl:attribute>
</data>
</xsl:template>
</xsl:stylesheet>
OK, I think this should work for you, allowing you to specify either attribute or element names in the $sortby parameter:
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby] | #*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
(you would just pass in "name" as the value of the $sortby parameter)
The problem was that the sort value node selection was only matching elements (* matches elements only).