XQuery to append node to existing node set - xslt

I am new to working with XQuery and XSLT. WebLogic is using XQuery 1.0.
I have a WebLogic composite and I have a collection of nodes and just want to add a node to an existing node set in an XQuery assignment. My current solution seems too heavyweight. I will be dealing with tens of thousands of records and it seems to me there should be a really easy way to just insert a node under another node and return it.
declare function local:func($svdPpl as element() (:: element(*, sv:svdPpl) ::),
$svdPsn as element() (:: element(*, sv:svdPsn) ::))
as element() (:: element(*, sv:svdPpl) ::) {
<sv:svdPpl>
{for $psn in $svdPpl return $psn} {$svdPsn}
</sv:svdPpl>
};
If I understand XQuery correctly that means millions of loops just to add records to existing node sets. I have tried using the insert node $svdPsn into $svdPpl syntax but that is causing validation errors in the WebLogic editor.
Any help is appreciated.

XQuery is a functional language and variables are immutable. It's not possible to change the value of a variable once an assignment has been made using standard XQuery.
There is an extension to XQuery that allows updates called XQuery Update Facility, but not all processors implement it. I'm not familiar with WebLogic, but it looks like some versions support it. That would be the direct route.
Failing that, a common pattern is to write a generic recursive typeswitch function that walks the tree and applies transformation logic as it generates a modified copy of the input. This is how things work implicitly in XSLT. It's unclear from your question exactly how that would be implemented, but just as an example:
declare function local:add-after(
$context as item(),
$target-name as xs:QName,
$payload as item()*
) as item()*
{
typeswitch ($context)
case element() return (
element { node-name($context) } {
$context/#*,
for $n in $context/node()
return local:add-after($n, $target-name, $payload)
},
if (node-name($context) = $target-name)
then $payload
else ())
default return $context
};
Given an XML node $xml with a value of:
<x>
<y>
<z></z>
</y>
</x>
You could use the above function local:add-after($xml, xs:QName('y'), <payload/>) to add a payload after the <y> element:
<x>
<y>
<z></z>
</y>
<payload></payload>
</x>

Related

xslt 2.0 : using HashMap

i have a java method that returns a map to xslt.
XSLT method call -
<xsl:variable name="mapValue" select="class:returnMap()">
Java Method -
public Map<String,List<String>> returnMap(){
Map<String,List<String>> map = new HashMap<String,List<String>>();
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
map.put("a",list);
map.put("b",list);
return map
}
how do I fetch the list value of the map in XSLT 2.0?
i tried assigning the values to a variable using the below format,
<xsl:variable name="valueOfA" select="$mapValue/entry[#key='a']"/>
<xsl:variable name="valueOfB" select="$mapValue/entry[#key='b']"/>
but, i got the below exception.
XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type java:java.util.LinkedHashMap
if i try to display the value of $mapValue it prints fine.
The error message shows that you are using Saxon. The rules for conversion between Java objects and XPath values depend on which processor you use, so this is relevant.
If you are using Saxon then you have access to XSLT 3.0, and to the new map/array data types, which means that you can achieve what you are trying to achieve without calling out to Java.
The specific error you are getting is because (as the message says) $mapValue is not a node, so you can't access its children using the "/" operator. If you use an XSLT 3.0 map instead then you can access an entry using, for example $mapValue?a.

XML Name space issue revisited

XML Name space issue revisited:
I am still not able to find a good solution to the problem that the findnode or findvalue does not work when we have xmlns has some value.
The moment I set manually xmlns="", it starts working. At least in my case. Now I need to automate this.
consider this
< root xmlns="something" >
--
---
< /root>
My recommended solution :
dynamically set the value to xmlns=""
and when the work is done automatically we can reset to the original value xmlns="something"
And this seems to be a working solution for my XMLs only but its stll manual.
I need to automate this:
How to do it 2 options:
using Perl regex, or
using proper LibXML setNamespace etc.
Please put your thought in this context.
You register the namespace. The point of XML is not having to kludge around with regexes!
Besides, it's easier: you create an XML::LibXML::XPathContext, register your namespaces, and use its find* calls with your chosen prefixes.
The following example is verbatim from a script of mine to list references in Visual Studio projects:
(...)
# namespace handling, see the XML::LibXML::Node documentation
my $xpc = new XML::LibXML::XPathContext;
$xpc->registerNs( 'msb',
'http://schemas.microsoft.com/developer/msbuild/2003' );
(...)
my $tree; eval { $tree = $parser->parse_file($projfile) };
(...)
my $root = $tree->getDocumentElement;
(...)
foreach my $attr ( find( '//msb:*/#Include', $root ) )
{
(...)
}
(...)
sub find { $xpc->find(#_)->get_nodelist; }
(...)
That's all it takes!
I only have one xmlns attribuite at the top of the XML once only so this works for me.
All I did was first to remove the namespace part i.e. remove the xmlns from my XML file.
NODE : for my $node ($conn->findnodes("//*[name()='root']")) {
my $att = $node->getAttribute('xmlns');
$node->setAttribute('xmlns', "");
last NODE;
}
using last just to make sure i come of the for loop in time.
And then once I am done with the XML parsing I will replace the
<root>
with
<root xmlns="something">
using simple Perl file operation or sed editor.

Reading XML data using boost::property_tree library functions in C++

<?xml version="1.0"?>
<sked>
<version>2</version>
<flight xmlns:xsi="some_uri" xsi:type="emirates">
<carrier>BA</carrier>
<number>4001</number>
<date>2011-07-21</date>
</flight>
<flight xmlns:xsi="some_uri" xsi:type="cathey-pacific">
<flight_class>
<type>Economy</type>
<fare>400</fare>
</flight_class>
<date>2011-07-21</date>
</flight>
</sked>
I have a XML document which describe 2 types of flights by same keywords. Sub fields are depend on the type of the flight. I have to read the XML and store it into C++ data classes according to the type of flights.
This is my code segment that is used for this purpose.
typedef boost::property_tree::ptree Node;
Node pt;
read_xml(test.xml, pt);
Node skedNode = pt.get_child("sked");
Node flightNode = skedNode.get_child("flight");
BOOST_FOREACH(Node::value_type const& v, skedNode.get_child("sked"))
{
if (v.first == "flight")
{
if (v.second.get("<xmlattr>.xsi:type", "Null") == "cathey-pacific")
{
BOOST_FOREACH(Node::value_type const& v1, flightNode.get_child("flight"))
{
if(v1.first == "flight_class")
FlightClass fclass = FlightClass(static_cast<Node>(flightNode));
}
}
}
}
When I try to run the above code, I got nothing inside FlightClass. I tried to debug the above code and found, v1.first is getting the values "carrier", "number" and "value" only.
I surprised because, those are the parameter of emirates type of flights. I couldn't receive cathey-pacific flight information.
Please help me to find out what the issue is.
I really want to get the information of cathy-pacific flights from this XML file and store into C++ data classes.
What should I do to correct this?
Note: Instead of second BOOST_FOREACH, I tried v.second.get_child("flight"); but it's throwing an exception. Then I replaced above by v.second.get_child("flight_class"); and it's giving it's sub-fields like: type and fare.
What may be the reason for that? It seems it's returning its grandchild nodes.
Boost doesn't provide any functionality like "get_next_child" to get different child nodes if there are more than one children nodes with same name.
So I just removed the unwanted fields from the tree before I iterate for above purpose.
flightNode.pop_front(); // to remove xmlattr
flightNode.pop_front(); // to remove version field
flightNode.pop_front(); // to remove first flight field.
Then used BOOST_FOREACH to reach above goal.

MSXML node.clode method does not work as expected

Using the MSXML2 functions from the "msxml3.dll" library, I'm trying to duplicate sections in an XML document, but it does not work as I expected.
Here is the XML:
<result>
<Target>
<Point>
<pos dimension="2">60.384005 5.333862</pos>
</Point>
</Target>
</result>
What I want is to add multiple sections. So I want to take the node, duplicate it and put it under the existing <Target> node. I'm almost convinced I should use the Clone method, but it does not seem to work.
The C++ code:
typedef MSXML2::IXMLDOMNodePtr XmlNode;
XmlNode pNode = pXMLRequest->selectSingleNode("//result");
if(pNode==NULL)
{ m_szErrorText = m_szErrorText + _T(" 'result' node not found");return FALSE;}
XmlNode pTargetNode = pNode->selectSingleNode("Target");
XmlNode pNewTargetNode = pTargetNode->cloneNode(true);
pNode->appendChild(pNewTargetNode);
But when I run this code nothing happens to the XML document. And when I inspect the XML text in pNewTargetNode I see it is '<result>' only which is just the name of the node While I would expect it to contain all the nodes in <Target>...</Target>. Is there something I am missing ?
I used the wrong kind of 'true'.
If I replace
XmlNode pNewTargetNode = pTargetNode->cloneNode(true);
with
XmlNode pNewTargetNode = pTargetNode->cloneNode(VARIANT_TRUE);
it works fine. I had already thought about this and used TRUE instead of true, but that does not work either.
So when using the msxml library alsways use VARIANT_BOOL, VARIANT_FALSE and VARIANT_TRUE.
Raymond Chen explains why there are so many variations:
http://blogs.msdn.com/b/oldnewthing/archive/2004/12/22/329884.aspx

Why can't I use xpath to parse nodes in the default namespace?

I'm an XML newbie and I have an XML document (which I can't edit because it comes from somewhere else) but it has a root node like this:
<Configuration xmlns="http://schemas.mycomp.com/product/settings" version="2.0.0">
I'm trying to parse this document with msxml and xpath and I've done it successfully if I remove the xmlns attribute. For some reason, with this xmlns attribute in place, the document won't parse. I've attempted to set the msxml parse to recognise the document using:
m_pXMLDoc->setProperty( _bstr_t(L"AllowDocumentFunction"), _variant_t(true));
m_pXMLDoc->setProperty( _bstr_t(L"AllowXsltScript"), _variant_t(true));
m_pXMLDoc->setProperty( _bstr_t(L"SelectionLanguage"), _variant_t(L"XPath"));
m_pXMLDoc->setProperty( _bstr_t(L"SelectionNamespaces"), _variant_t(L"xmlns='http://schemas.mycomp.com/product/settings'"));
m_pXMLDoc->preserveWhiteSpace = VARIANT_FALSE;
m_pXMLDoc->resolveExternals = VARIANT_TRUE;
m_pXMLDoc->validateOnParse = VARIANT_FALSE;
From reading around it looks like xpath only works on the "no name" namespace and this document sets the default namespace so that it's no longer "no name". Can I set the namespace that xpath uses using MSXML?
From Microsoft : This behaviour is by design ...
See http://support.microsoft.com/kb/288147
Use prefixes with the namespaces when you specify the SelectionNamespaces property