I'm saving some timestamps in my XML results in standard UTC format.
What I'd like to be able to do is re-convert this to human readable times. Without the timezone addendum. As far as I've been able to get so far is:
format-dateTime(
xs:dateTime(
adjust-dateTime-to-timezone(
xs:dateTime(#thevalue),xs:dayTimeDuration('P0DT4H')
)
),'[M01]/[D01]/[Y0001] [H01]:[m01]:[s01]'
)
where #thevalue is like: 2006-02-15T17:00:00
It's giving me a headache because the formatter returns a time of 17:00. If I peel back a layer of the format-dateTime to see what the adjust-dateTime function returns, it gives
2006-02-15T17:00:00+04:00
... and all I really want to see is 21:00... so very frustrated. Anyone deal with this before?
Here is a transformation that does what you want:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDateTime" as="xs:dateTime"
select="xs:dateTime('2006-02-15T17:00:00+00:00')"/>
<xsl:template match="/">
<xsl:sequence select=
"adjust-dateTime-to-timezone($vDateTime,
xs:dayTimeDuration('P0DT4H')
)"/>
</xsl:template>
</xsl:stylesheet>
When applied to any XML document (not used), the result is:
2006-02-15T21:00:00+04:00
And the complete solution is:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDateTime" as="xs:dateTime"
select="xs:dateTime('2006-02-15T17:00:00+00:00')"/>
<xsl:template match="/">
<xsl:variable name="vadjustedDateTime" select=
"adjust-dateTime-to-timezone($vDateTime,
xs:dayTimeDuration('P0DT4H')
)"/>
<xsl:sequence select=
"format-dateTime($vadjustedDateTime,
'[M01]/[D01]/[Y0001] [H01]:[m01]:[s01]'
)
"/>
</xsl:template>
</xsl:stylesheet>
which produces this result:
02/15/2006 21:00:00
Related
Could some one please suggest me out to calculate 1 year back old date time from the current date time which is in EDT format using XSLT 1.0.
I know using xslt 2.0 there are function like 'yearMonthDuration' but need to perform this in xslt 1.0.
I have this
'''
2021-09-21
11:14:20 EDT
'''
I need to get as 20200921111420 EDT. This is the format I will need to convert as shown YYYYMMDDTTMMSS EDT
Please help in this regard.
Can I extract year column and add 365 to this will that work?
If all you want to do is add 1 year to a given date, you could start by extracting the year component and simply adding 1 to it, leaving the month and day components as they are. However, this will produce an invalid date if the given date is February 29 in a leap year. Still, it's rather simple to correct for that - say something like:
<xsl:value-of select="substring($given-date, 1, 4) + 1"/>
<xsl:variable name="md" select="substring($given-date, 5, 10)" />
<xsl:choose>
<xsl:when test="$md='-02-29'">-02-28</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$md"/>
</xsl:otherwise>
</xsl:choose>
Added:
I have this ''' 2021-09-21 11:14:20 EDT ''' I need to get as 20200921111420 EDT
That's equally simple:
<xsl:value-of select="substring($given-date, 1, 4) - 1"/>
<xsl:variable name="mdt" select="translate(substring($given-date, 6, 14), '-: ', '')" />
<xsl:choose>
<xsl:when test="starts-with($mdt, '0229')">
<xsl:text>0228</xsl:text>
<xsl:value-of select="substring($mdt, 5)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$mdt"/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="substring($given-date, 20)"/>
EXSLT.Date has some support e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:date="http://exslt.org/dates-and-times"
exclude-result-prefixes="xs date">
<xsl:template match="date">
<xsl:copy>
<xsl:value-of select="concat(., ' - ', date:add(., 'P1Y'))"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
gives <date>2021-09-22 - 2022-09-22</date> for an input of e.g. <date>2021-09-22</date>.
So check whether you use processor supporting that or can use an extension to your XSLT processor, for .NET XslCompiledTransform there is one with https://www.nuget.org/packages/Mvp.Xml.NetStandard/.
I have a fairly deep xml file of travel data that I've anonymized here. I would like to pull out the coupon statuses for multiple segments and attach them to the itinerary id. I'm having a very difficult time using the xml2 package and I think the reason why is that some of my XML data terminates with text and some terminate with attributes. I've tried to convert the xml to a list with as_list(). I've also tried to start with xml_find_all() but get a nodeset of 0 regardless of the node I search for (Ticketing or Coupons should work, for example). Below is the data:
<?xml version="1.0" encoding="UTF-8"?>
<eTicketCouponRS xmlns="http://webse" xmlns:ns4="http://s" xmlns:stl="http://se" Version="2.0.0">
<stl:ApplicationResults status="Complete">
<stl:Success timeStamp="2021-06-16T11:39:52-05:00" />
</stl:ApplicationResults>
<TicketingInfos>
<TicketingInfo>
<Ticketing AgencyCity="DCA" AgentWorkArea="A" IATA_Number="0952" IssuingAgent="A" PrimeHostID="1S" PseudoCityCode="5SE0" TransactionDateTime="2021-06-16T11:39">
<CouponData InformationSource="S" IssueDate="2021-03-29" NumBooklets="1" TicketMedia="E" TicketMode="63">
<AirItineraryPricingInfo>
<FareCalculation>
<Text>SAN AA X/E/DFW AA TYO M0.00NUC0.00END ROE1.00 XFSAN4.5DFW4.5</Text>
</FareCalculation>
<ItinTotalFare>
<BaseFare Amount="0.00" CurrencyCode="USD" />
<Taxes>
<Tax Amount="19.10" TaxCode="US" />
<Tax Amount="5.60" TaxCode="AY" />
<Tax Amount="9.00" TaxCode="XF" />
</Taxes>
<TotalFare Amount=".70" CurrencyCode="USD" />
</ItinTotalFare>
<PassengerTypeQuantity Code="GV1" />
</AirItineraryPricingInfo>
<Coupons>
<Coupon CodedStatus="OK" Number="1" StatusCode="RFND">
<FlightSegment DepartureDateTime="2021-08-13T06:15" FlightNumber="2535" RPH="1" ResBookDesigCode="V">
<DestinationLocation LocationCode="DFW" />
<FareBasis Code="VCA" />
<MarketingAirline Code="AA" FlightNumber="2535" />
<OperatingAirline Code="AA" />
<OriginLocation LocationCode="SAN" />
</FlightSegment>
</Coupon>
<Coupon CodedStatus="OK" Number="2" StatusCode="RFND">
<FlightSegment ConnectionInd="X" DepartureDateTime="2021-08-13T12:20" FlightNumber="175" RPH="2" ResBookDesigCode="V">
<DestinationLocation LocationCode="HND" />
<FareBasis Code="VCA" />
<MarketingAirline Code="AA" FlightNumber="175" />
<OperatingAirline Code="AA" />
<OriginLocation LocationCode="DFW" />
<FareTypeClass>PG</FareTypeClass>
<FareTypeRule>OW-GO</FareTypeRule>
</FlightSegment>
</Coupon>
</Coupons>
<CustomerInfo>
<Customer>
<Invoice Number="126" />
<Payment ApprovalID="03" RPH="1" ReferenceNumber="XXXXXXXXXXXX" Type="CC">
<CC_Info>
<PaymentCard Code="VI" ExpirationDate="XX-XX" />
</CC_Info>
</Payment>
<PersonName NameReference="PCS" PassengerType="GV1">
<GivenName>VER</GivenName>
<Surname>DE</Surname>
</PersonName>
</Customer>
</CustomerInfo>
<ItineraryRef CustomerIdentifier="R5" ID="EXAMPLE" />
</CouponData>
</Ticketing>
</TicketingInfo>
</TicketingInfos>
</eTicketCouponRS>
I have about 100 of these each to load separately and pull out a small table consisting of the following columns:
SuccTimeStamp TransacTimeStamp ItineraryID CouponNumber StatusCode Origin Destination OperatingAirline FlightNumber.
You can see that each of these elements are found at different depths of the xml and every travel itinerary has a different number of coupons, anywhere from 1-10. I also found a helpful post here from hrbrmstr helping out someone from 2018, but I can't get a similar solution to "see" my nodes and I'm not sure if it's my code or my xml data.
Any help is appreciated!
For nested XML files which you need to flatten for end use needs such as R, consider XSLT, the special-purpose language designed to transform XML files. You can run XSLT 1.0 scripts in R using the xslt package (sister to xml2). Alternatively, you can use a dedicated XSLT processor and have R call the external program with system(). Like SQL, XSLT is an industry, portable language not limited to R.
Within XSLT, since your granularity is coupon, you can extract from <Coupon> level and use the ancestor:: XPath axe to retrieve higher up level information. Due to the default namespace needs, the long-windeed <xsl:element> is used. IATA_Number is assumed to be ItineraryID.
XSLT (save as .xsl file, a special .xml file)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:w="http://webse"
xmlns:stl="http://se">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/w:eTicketCouponRS">
<xsl:copy>
<xsl:apply-templates select="descendant::w:Coupon"/>
</xsl:copy>
</xsl:template>
<xsl:template match="w:Coupon">
<xsl:copy>
<xsl:element name="SuccTimeStamp" namespace="http://webse">
<xsl:value-of select="ancestor::w:eTicketCouponRS/stl:ApplicationResults/stl:Success/#timeStamp"/>
</xsl:element>
<xsl:element name="TransacTimeStamp" namespace="http://webse">
<xsl:value-of select="ancestor::w:Ticketing/#TransactionDateTime"/>
</xsl:element>
<xsl:element name="ItineraryID" namespace="http://webse">
<xsl:value-of select="ancestor::w:Ticketing/#IATA_Number"/>
</xsl:element>
<xsl:element name="CouponNumber" namespace="http://webse">
<xsl:value-of select="#Number"/>
</xsl:element>
<xsl:element name="StatusCode" namespace="http://webse">
<xsl:value-of select="#CodedStatus"/>
</xsl:element>
<xsl:element name="Origin" namespace="http://webse">
<xsl:value-of select="w:FlightSegment/w:OriginLocation/#LocationCode"/>
</xsl:element>
<xsl:element name="Destination" namespace="http://webse">
<xsl:value-of select="w:FlightSegment/w:DestinationLocation/#LocationCode"/>
</xsl:element>
<xsl:element name="OperatingAirline" namespace="http://webse">
<xsl:value-of select="w:FlightSegment/w:OperatingAirline/#Code"/>
</xsl:element>
<xsl:element name="FlightNumber" namespace="http://webse">
<xsl:value-of select="w:FlightSegment/#FlightNumber"/>
</xsl:element>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Online Demo
R
library(xml2)
library(xslt)
# LOAD XML AND XSLT
doc <- read_xml("/path/to/Input.xml")
style <- read_xml("/path/to/Style.xsl", package = "xslt")
# RUN TRANSFORMATION AND SEE OUTPUT
flat_xml <- xml_xslt(doc, style)
cat(as.character(flat_xml))
# RETRIEVE data NODES
nmsp <- c(w = "http://webse")
recs <- xml2::xml_find_all(flat_xml, "//w:Coupon", ns=nmsp)
# BIND EACH CHILD TEXT AND NAME
df_list <- lapply(recs, function(r) {
vals <- xml2::xml_children(r)
data.frame(rbind(setNames(c(xml2::xml_text(vals)),
c(xml2::xml_name(vals)))))
})
# COMBINE ALL DFS
final_df <- do.call(rbind.data.frame, df_list)
rm(recs, df_list)
final_df
# SuccTimeStamp TransacTimeStamp ItineraryID CouponNumber StatusCode Origin Destination OperatingAirline FlightNumber
# 1 2021-06-16T11:39:52-05:00 2021-06-16T11:39 0952 1 OK SAN DFW AA 2535
# 2 2021-06-16T11:39:52-05:00 2021-06-16T11:39 0952 2 OK DFW HND AA 175
Above runs for a single XML. For 100 separate files, wrap above in a user-defined method and run lapply for a list of XML dataframes for master concatenation at very end. Load XSLT once outside loop since it does not change, assuming XML files retain same structure.
style <- read_xml("/path/to/Style.xsl", package = "xslt")
xml_to_df <- function(xml_file) { ... }
xml_dfs <- lapply(list_of_xml_files, xml_to_df)
master_df <- do.call(rbind.data.frame, xml_dfs)
Thanks Parfait! I was able to modify the template xsl that you provided. The xsl sheet seems to "parse" everything nicely! After I got it "flattened", all I simply had to do was as_list(), as_tibble() and unnest() a couple of times and then it was a data frame.
Thanks!
The XML file contains Accounts and a list of Account (contains ID and AccountDescription).
In the below example, there are 2 Account.
<?xml version="1.0"?>
<Accounts>
<Account>
<ID>5</ID>
<AccountDescription>Account Description 5</AccountDescription>
</Account>
<Account>
<ID>8</ID>
<AccountDescription>Account Description 8</AccountDescription>
</Account>
</Accounts>
When using the below XSL, it creates a PDF file with 2 pages, and each page has the header ID and AccountDescription, but there is no data/content underneath it, like this:
On page 1:
ID AccountDescription
On page 2:
ID AccountDescription
I would like to show the data like this:
ID AccountDescription
5 Account Description 5
8 Account Description 8
How can I do that ? Thank you.
This is my current XSL:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="Accounts">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master
master-name="main"
margin-top="0px"
margin-bottom="0px"
margin-left="18px"
margin-right="18px">
<fo:region-body margin-top="0.75in" margin-bottom="2in" margin-left="18px" margin-right="18px"/>
<fo:region-before extent="0.75in"/>
<fo:region-after extent="1.5in"/>
<fo:region-end extent="75px"/>
</fo:simple-page-master>
</fo:layout-master-set>
<xsl:apply-templates select="Account"/>
</fo:root>
</xsl:template>
<xsl:template match="Account">
<fo:page-sequence master-reference="main">
<fo:flow flow-name="xsl-region-body">
<fo:table font-size="10pt">
<fo:table-column column-width="15mm"/>
<fo:table-column column-width="55mm"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="ID"/></fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="AccountDescription"/></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</xsl:template>
</xsl:stylesheet>
How can I show all data in the same page ?
You need to use only one fo:page-sequence. Move it from the Account template up into the Accounts template.
Updated XSLT
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="Accounts">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master
master-name="main"
margin-top="0px"
margin-bottom="0px"
margin-left="18px"
margin-right="18px">
<fo:region-body margin-top="0.75in" margin-bottom="2in" margin-left="18px" margin-right="18px"/>
<fo:region-before extent="0.75in"/>
<fo:region-after extent="1.5in"/>
<fo:region-end extent="75px"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="main">
<fo:flow flow-name="xsl-region-body">
<xsl:apply-templates select="Account"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="Account">
<fo:table font-size="10pt">
<fo:table-column column-width="15mm"/>
<fo:table-column column-width="55mm"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="ID"/></fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="AccountDescription"/></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
</xsl:stylesheet>
I have the following XML that I need to transform:
<?xml version="1.0" encoding="utf-8"?>
<TestRecords>
<TestData>
<Users>
<User>
<Id>BG123</Id>
<Name>Bill Gates</Name>
</User>
<User>
<Id>SN123</Id>
<Name>Satya Nadella</Name>
</User>
</Users>
<UserDetails>
<UserDetail>
<UserId>SN123</UserId>
<CompanyName>Microsoft Corp</CompanyName>
</UserDetail>
<UserDetail>
<UserId>
<UserId>BG123</UserId>
<CompanyName>Bill Gates Foundation</CompanyName>
</UserId>
</UserDetail>
</UserDetails>
I need to map this XML into the following XML:
<?xml version="1.0" encoding="utf-8"?>
<TestRecords>
<TestData>
<Users>
<User>
<Id>BG123</Id>
<Name>Bill Gates</Name>
<CompanyName>Bill Gates Foundation</CompanyName>
</User>
<User>
<Id>SN123</Id>
<Name>Satya Nadella</Name>
<CompanyName>Microsoft Corp</CompanyName>
</User>
</Users>
</TestData>
</TestRecords>
When I loop over Users/User, I need to find the UserDetail where UserDetail/UserId is equal to the current User/Id
Thank you and best regards
Michael
If you don't want to do Custom XSLT as suggested by FCR the only other option when you have different looping structures is to have an intermediate schema and two maps.
Which produces
<TestRecords>
<TestData>
<Users>
<User>
<Id>BG123</Id>
<Name>Bill Gates</Name>
<UserDetails>
<UserID>SN123</UserID>
<CompanyName>Microsoft Corp</CompanyName>
</UserDetails>
<UserDetails>
<UserID>BG123</UserID>
<CompanyName>Bill Gates Foundation</CompanyName>
</UserDetails>
</User>
<User>
<Id>SN123</Id>
<Name>Satya Nadella</Name>
<UserDetails>
<UserID>SN123</UserID>
<CompanyName>Microsoft Corp</CompanyName>
</UserDetails>
<UserDetails>
<UserID>BG123</UserID>
<CompanyName>Bill Gates Foundation</CompanyName>
</UserDetails>
</User>
</Users>
</TestData>
</TestRecords>
Which you can then run through this second map to produce the desired outcome.
This will become very inefficient however if the second list is large.
This is a common lookup pattern in xslt and there is also the opportunity to use xsl:key to create an index which can boost performance on large documents. Refer here if you need to convert a .btm to xslt.
(Also, I'm assuming that there isn't a double wrapper UserId at on the last UserDetails/UserDetail element):
<?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="userLookup"
match="/TestRecords/TestData/UserDetails/UserDetail" use="UserId"/>
<!--identity template - copy everything by default -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!--i.e.match only Users in the first Users/User tree. Actually the explicit
ancestor qualifier is redundant because of the other suppress template -->
<xsl:template match="User[ancestor::Users]">
<User>
<xsl:copy-of select="child::*" />
<CompanyName>
<xsl:value-of select="key('userLookup', Id)/CompanyName"/>
</CompanyName>
</User>
</xsl:template>
<!--Suppress the second userdetails part of the tree entirely -->
<xsl:template match="UserDetails" />
</xsl:stylesheet>
Fiddle here
I need to loop through an XML document (no problem over there) and check if a value that i find is already in a (a) tag in a div in my XSL document that i am generating, only if the value is not in that (a) tag i should create a new (a) tag for it and put in in the div that i am checking...
Any one knows how to do it dynamically in XSLT?
<div id="tags"><span class="l_cap"> </span>
all
<xsl:for-each select="root/nodes/node/data/genres">
<xsl:for-each select="value">
**<xsl:if test="not(contains())">**
<xsl:value-of select="current()"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
sorry for before, what i am trying to do is: in the if statement, check if the current value is already exist in the div if not, add it, if is, don't do anything...
10x again
It sounds like you're trying to create a distinct list of all of the "genres" in your list.
Assuming a data structure which looks a bit like this:
<root>
<nodes>
<node>
<data>
<genres>
<value>One</value>
<value>Two</value>
<value>Two</value>
<value>Three</value>
<value>Two</value>
</genres>
</data>
</node>
</nodes>
</root>
And a stylesheet which looks a bit like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="genres" match="value" use="."/>
<xsl:template match="/">
<div>
<xsl:for-each select="/root/nodes/node/data/genres/value">
<xsl:if test="generate-id(.) = generate-id(key('genres', .)[1])">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
Then you will end up with something like this:
<div>
One
Two
Three
</div>
This is a fairly standard XSLT 1.0 technique. It uses keys (described here: http://www.xml.com/pub/a/2002/02/06/key-lookups.html ) to create a sort of index of all the /root/nodes/node/data/genres/value entries. Then it loops through all of the entries, but only prints the first one of each type. The end result is that each value will only be output once.