How to select siblings (xpath syntax) with Perl's XML::Twig? - perl

I need to select the next node via next_sibling or first_elt. But I want to filter by node name (containing the string "TON")
first_elt ('HILTON[#method]' or 'SHERATON[#method]');
or
next_sibling ('HILTON[#method]' or 'SHERATON[#method]');
or
next_sibling ('TON[#method]');
Example I tried (not working):
#!/usr/bin/perl -w
use warnings;
use XML::Twig;
$t-> parsefile ('file.xml');
my $Y0=$t->first_elt('HILTON[#method]' or 'SHERATON[#method]');
it will just process for 'HILTON[#method]'
my $Y0=$t->first_elt('/*TON[#method]');
wrong navigation condition '/*TON[#method]' () at C:/strawberry/perl/site/lib/XML/Twig.pm line 3523

As this is outside of the XPath subset supported by XML::Twig, you have to use a custom filter, by passing code to first_elt:
$t->first_elt( sub { $_[0]->tag=~ m{TON$} && $_[0]->att( 'method') })
This returns the first element for which the sub returns a true value.
The need for such an expression is a bit troubling though. In your example you define a class of elements by the fact that their name ends in TON. What happens when you have a CARLTON element? Or when MARRIOTT elements need to be processed with SHERATON and HILTON? Do you need to rewrite your queries?
If you are the one designing the format of the data, I would suggest revising the format. HILTON and SHERATON should probably be attributes of a HOTEL, BRAND or OWNER tag. Maybe an additional attribute would be useful, to mark that both types should be processed similarly. This attribute would only make sense if it is a property intrinsic to the data.
If the data is what it is and you have no input on its format, then I would have a list of the tags to process and check on these:
my %TAGS_TO_PROCESS= map { $_ => 1 } qw( HILTON SHERATON);
my $elt= $t->first_elt( sub { $TAGS_TO_PROCESS{$_[0]->tag} && $_[0]->att( 'method') })
This way adding/substracting other tags is easy.

Use:
*[substring(name(), string-length(name()) - 2) = 'TON'][#method][1]
Explanation:
This expression uses an XPath 1.0 equevalent for the XPath 2.0 standard function ends-with():
The XPath 1.0 equivalent of the XPath 2.0 expression:
ends-with($s, $s2)
is:
substring($s, string-lenth() - string-length($s2) + 1) = $s2
In this last expression we substitute $s with name() and $s2 with 'TON'
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy-of select=
"*[substring(name(), string-length(name()) - 2) = 'TON'][#method] "/>
==========
<xsl:copy-of select=
"*[substring(name(), string-length(name()) - 2) = 'TON'][#method][1] "/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<HILTON method="buy"/>
<TON method="burn"/>
<TONIC method="drink"/>
<HILTON nomethod="yes"/>
<SHERATON/>
<SHERATON method="visit"/>
</t>
the transformation evaluates the two XPath expressions and the selected nodes are copied to the output:
<HILTON method="buy"/>
<TON method="burn"/>
<SHERATON method="visit"/>
==========
<HILTON method="buy"/>
The first expression selects all elements - children of the context node, whose name ends with "TON" and that also have a method attribute.
The second expression selects the first node from those, selected by the first expression.

Related

How to write dynamic attributes

I need to write a dynamic Attribute name instead of hardcode of Name attribute in dataweave 2.0 mulesoft 4 in anypointstudio
<?xml version="1.0" encoding="UTF-8"?>
<iGoApplicationData>
<UserData>
<Data Name="UpdateUserProfile">True</Data>
<Data Name="Action">??</Data>
</iGoApplicationData>
So in order to generate an XML like yours the DW structure will look like
{
iGoApplicationData: {
UserData: {
Data #(Name: payload.foo): "True",
Data #((var.attributeName): "Action"): "??"
}
}
}
So in this example I show how to specify a value in the attribute or a dynamic attribute name. For the dynamic attribute value just type the expression on the value side of the attribute (the part that goes after the :)
For dynamic attribute name you need to wrap the expression between parenthesis. When the name is wrapped between parenthesis it is considered dynamic. This applies to object keys and attributes names

Xtext and EMF modeling - Opposite relations parsing

I'm currently playing around with XText and EMF and I've come to a dead end.
I have an ecore model that I created using the diagram editor. I don't provide the XML representation; it should be clear from the example. Some of the relations are opposite to each other (like the parent-children relation).
This binding works perfectly fine when I create the instances programatically. Below, I show a test case that is successfully passed.
However, when I parse the model using XText, these opposite relations are not set. I can't find a way to fix this. The relations are strictly one-directional as they appear in the input file. Is there any way to force Xtext to set these? or am I supposed to resolve these manually?
Passing test
WordsFactory factory = WordsFactory.eINSTANCE;
// Prepare a simple dictionary hierarchy
Dictionary d = factory.createDictionary();
Synset s = factory.createAdjectiveSynset();
s.setDescription("A brief statement");
s.setExample("He didn't say a word.");
WordSense ws = factory.createAdjectiveWordSense();
Word w = factory.createWord();
w.setName("word");
ws.setWord(w);
s.getWordSenses().add(ws);
d.getWords().add(w);
d.getSynsets().add(s);
// Now check the bidirectional links
Assert.assertTrue(ws.getSynset() == s);
Assert.assertTrue(w.getSenses().get(0) == ws);
XMI representation of this example
<?xml version="1.0" encoding="ASCII"?>
<words:Dictionary xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:words="http://www.example.com/dstahr">
<words senses="//#synsets.0/#wordSenses.0" name="word"/>
<synsets xsi:type="words:AdjectiveSynset" description="A brief statement" example="He didn't say a word.">
<wordSenses xsi:type="words:AdjectiveWordSense" word="//#words.0"/>
</synsets>
</words:Dictionary>
Grammar definition (some unimportant rules removed)
grammar ocs_assignment.dsl.DSL with org.eclipse.xtext.common.Terminals
import "platform:/resource/ocs_assignment.model/model/words.ecore"
import "http://www.eclipse.org/emf/2002/Ecore" as ecore
Dictionary returns Dictionary:
{Dictionary}
'dict' name=EString
('add words' '[' words+=Word* ( "," words+=Word)* ']')?
synsets+=Synset*
;
Synset returns Synset:
AdjectiveSynset | NounSynset | VerbSynset;
WordSense returns WordSense:
AdjectiveWordSense | NounWordSense | VerbWordSense;
Word returns Word:
name=EString;
EString returns ecore::EString:
STRING | ID;
NounWordSense returns NounWordSense:
word=[Word|EString];
NounSynset returns NounSynset:
{NounSynset}
'(N)' name=EString
'{'
'content' '[' (wordSenses+=NounWordSense ( "," wordSenses+=NounWordSense)*)? ']'
'description' description=EString
'example' example=EString
('hyponym' hyponym=[Synset|EString])?
('hypernym' hypernym=[Synset|EString])?
('similarTo' '(' similarTo+=[Synset|EString] ( "," similarTo+=[Synset|EString])* ')' )?
'}';
Parsed file
dict dict
add words test1 test2 test3
(N) test1
{
content [ test1 test2 ]
description "test1"
example "test1"
}
(N) test2
{
content [ test3 ]
description "test2"
example "test2"
hypernym test1
}
XMI representation of the parsed file (missing references for Words)
<?xml version="1.0" encoding="ASCII"?>
<words:Dictionary xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:words="http://www.example.com/dstahr">
<words name="test1"/>
<words name="test2"/>
<words name="test3"/>
<synsets xsi:type="words:NounSynset" description="test1" example="test1" name="test1">
<wordSenses xsi:type="words:NounWordSense">
<word href="importedFile1.wdsl#xtextLink_::0.1.0.2.0::1::/0"/>
</wordSenses>
</synsets>
<synsets xsi:type="words:NounSynset" description="test2" example="test2" name="test2">
<hypernym xsi:type="words:AdjectiveSynset" href="importedFile1.wdsl#xtextLink_::0.1.1::1::/21"/>
<wordSenses xsi:type="words:NounWordSense">
<word href="importedFile1.wdsl#xtextLink_::0.1.1.2.0::1::/0"/>
</wordSenses>
</synsets>
</words:Dictionary>
Xtext does not support inverse relations built-in. If you generate the metamodel from your grammar, you would see that the inverse is not set for the corresponding relations. However, if your Ecore model has the inverse relation property is set, Xtext will maintain that.
There are two ways to use such a metamodel:
Create your own Ecore model and reference it.
You could define an IXtext2EcorePostProcessor instance that can update your generated metamodel before it is serialized. For details, see the corresponding blog post of Christian Dietrich.
The first solution is quite easy, but you would lose the auto-generated EMF models. In the second case, you have to write code that updates the Ecore (meta)model before it is serialized that can be really tricky to achieve. We have done the second approach for EMF-IncQuery (for another kind of customization), as at that point the autogeneration was really important, however the customizer became really hard to understand since.

what are restrictions on using parse in xml-twig to parse xml string

I am trying to use $twig->parse($xmlstring) to add id attribute to all xml elements in string. Each string is an element of an array that looks something like:
<classes name="Test::Class">
<public_methods>
<members const="no" kind="function" name="RegisterDefinition" volatile="no">
<parameters declaration_name="name" type="std::string"/>
<parameters declaration_name="description" type="std::string"/>
</members>
</public_methods>
</classes>
In script I use foreach loop to get each element and create id attribute.
foreach my $str (#newonly) {
$twig->parse( $str );
}
I create twig object and TwigHandler that calls routine and it only works if I use parsefile and specify xml file. Does not work if I use parse to parse part of xml file that is element of an array.
Routine I am using is:
my $twig = XML::Twig->new(
TwigHandlers => {
'_all_' => \&add_id,
},
);
sub add_id
{ my($twig, $element)= #_;
$element->set_id($id++);
$twig->purge;
}
From the documentation:
parse ( $source)
The $source parameter should either be a string containing the whole XML document, or it should be an open IO::Handle (aka a filehandle).
I am confused about the use of $twig->purge at the end of the handler. When you do this, you lose all of the structure you've just built. I think you do create the id's, but you loose them when you purge the twig.
You should probably remove the call to purge and add a handler on classes, that would use the twig for whatever you need to use it for, and then purge it, once you're done with it.

Linq to XML returns no items when there are items in the XML

I am trying to query some XML data using Linq because it's easier than using XPath and as a good "proof of concept" for my co-workers as to how we can use Linq. Here is my XML:
<Booking>
<ServiceCollection>
<Service>
<BookingID>10508507</BookingID>
<AdditionalChargeID>1</AdditionalChargeID>
<ServiceName>Fuel Surcharge</ServiceName>
<ServiceCost>56.87</ServiceCost>
<ServiceCharge>103.41</ServiceCharge>
<showInNotes>0</showInNotes>
<showInHeader>0</showInHeader>
<BOLHeaderText />
</Service>
<Service>
<BookingID>10508507</BookingID>
<AdditionalChargeID>2</AdditionalChargeID>
<ServiceName>Lift Gate at Pickup Point</ServiceName>
<ServiceCost>25.00</ServiceCost>
<ServiceCharge>42.00</ServiceCharge>
<showInNotes>1</showInNotes>
<showInHeader>1</showInHeader>
<BOLHeaderText>Lift Gate at Pickup Point</BOLHeaderText>
</Service>
</ServiceCollection>
</Booking>
Now, here is my C# code (ignore the Conversions class; they simply make sure a default value is returned if the item is null):
var accessorials = from accessorial in accessorialsXml.Elements("ServiceCollection").Elements("Service")
select new Accessorial
{
BookingID = Conversions.GetInt(accessorial.Element("BookingID").Value),
Name = accessorial.Element("ServiceName").Value,
Cost = Conversions.GetDecimal(accessorial.Element("ServiceCost").Value),
Charge = Conversions.GetDecimal(accessorial.Element("ServiceCharge").Value),
ShowInNotes = Conversions.GetBool(accessorial.Element("showInNotes").Value),
ShowInHeader = Conversions.GetBool(accessorial.Element("showInheader").Value),
BillOfLadingText = accessorial.Element("BOLHeaderText").Value
};
return accessorials.ToList();
I have a Unit test which is failing because the count of accessorials (the "Service" node in the XML) is 0 when it should be 2. I tested out this same code in LinqPad (returning an anonymous class instead of an actual entity) and it is returning the proper number of values, yet the code here returns no objects.
Any ideas?
The bug may lie in how you're getting accessorialsXml in the first place. Try outputting the contents of this object before doing the query, to make sure it's exactly the same as the string you're using in LINQPad.

Control XML Serialization from DataSet -- Attributes on "TableName element"

Hope I chose the correct forum.
I have a dataset object with one table that comes to me from a common custom component's GetDS method. I need to pass XML to another process (chunked as byte array). I have it all working but the XML is missing some attributes that the consuming process expects.
I create a dataset object and can control the name of the TableName (root element) and the row like this:
da.Fill(ds, "Foo")
ds.DataSetName = "FooUpload"
I use the GetXML method to serialize to XML that looks like the following:
<?xml version="1.0" standalone="yes" ?>
<FooUpload>
<Foo>
<FooMasterID>483</FooMasterID>
<Country>27</Country>
<PaymentCode>ANN</PaymentCode>
<Amount>132</Amount>
<PaidDate>2012-12-31 00:00:00</PaidDate>
<PaidBy>FooServices</PaidBy>
</Foo>
</FooUpload>
The calling process expects
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<FooUpload **ClientCode="FOOO" RecordCount="1" CreateDate="2008-12-09T15:02:18.920" CreateUser="valli"**>
<Foo>
<FooMasterID>483</FooMasterID>
<Country>27</Country>
<PaymentCode>ANN</PaymentCode>
<Amount>132</Amount>
<PaidDate>2012-12-31 00:00:00</PaidDate>
<PaidBy>FooServices</PaidBy>
</Foo>
</FooUpload>
Note the attributes on the FooUpload element. This node is the name of the DataTable in the DataSet.
I have searched for how to control the XMLSerializer and find lots of examples for custom objects. I even found examples of setting the column mapping to be MappingType.Attribute which is close but I need to do this with the root element which is actually the TableName for the dataset.
I feel that I am close and if I do not find a more elegant solution I will have to create a hack like looping and spitting out changed string plus rest of the XML.
You can feed the output of GetXML into a XmlDocument and add the attributes afterwards. For example:
XmlDocument xdoc = new XmlDocument();
xdoc.LoadXml(ds.GetXml());
XmlAttribute attr=xdoc.CreateAttribute("ClientCode");
attr.Value = "FOOOO";
xdoc.DocumentElement.Attributes.Append(attr);
Then you can save the xdoc into a file, or put it into a string, for example:
XmlTextWriter xw = new XmlTextWriter(new MemoryStream(),Encoding.UTF8);
xdoc.Save(xw);
xw.BaseStream.Position = 0;
StreamReader sr = new StreamReader(xw.BaseStream);
string result = sr.ReadToEnd();
This looks pretty much as what you are looking for.