How do I write multiple items with Zend_Config_Writer_Xml? - zend-framework

I am trying to write a XML file with Zend_Config_Writer_Xml. I found out a problem that I can't write multiple items under a root. I would like to do,
<root>
<item name="test"></item>
<item name="test2"></item>
</root>
I can't find a method to do this on zend documentation.
Please advise me.

Standard writer doesn't do exactly that, but it works like this: if you do:
$config = new Zend_Config(array(), true);
$config->root = array("test1" => 1, "test2" => array(1,2));
$writer = new Zend_Config_Writer_Xml();
$writer->write('config.xml', $config);
then what you get is:
<zend-config xmlns:zf="http://framework.zend.com/xml/zend-config-xml/1.0/">
<root>
<test1>1</test1>
<test2>1</test2>
<test2>2</test2>
</root>
</zend-config>
I don't think standard writer does attributes, you'd need to override it for that.

Related

How to suppress root element in BeanIO XML generation?

When generating a XML file with BeanIO, the StreamBuilder name is used as root element. How to suppress this root element?
Example:
StreamBuilder builder = new StreamBuilder("builder_name")
.format("xml")
.parser(new XmlParserBuilder()).addRecord(Test.class);
The Test class:
#Record
public class Test {
#Field(at=0)
private String field1 = "ABC";
// Getters and Setters ...
}
The generated XML file:
<builder_name>
<Test>
<field1>ABC</field1>
</Test>
</builder_name>
I don't want builder_name to be showed as root element. I want Test element to be the root. How can I achieve that?
You need to set the xmlType property/attribute to XmlType.NONE on your stream configuration.
The answer is in Appendix A - XML Mapping file reference of the documentation.
xmlType - The XML node type mapped to the stream. If not specified or set to element, the stream is mapped to the root element of the XML document being marshalled or unmarshalled. If set to none, the XML input stream will be fully read and mapped to a child group or record.
The trick is now to translate that piece of information into the StreamBuilder API, which is:
xmlType(XmlType.NONE)
Your example then becomes:
StreamBuilder builder = new StreamBuilder("builder_name")
.format("xml")
.xmlType(XmlType.NONE)
.parser(new XmlParserBuilder())
.addRecord(Test.class);
Which produces this unformatted/unindented output:
<?xml version="1.0" encoding="utf-8"?><test><field1>ABC</field1></test>
To have the xml formatted (pretty print)/indented use:
StreamBuilder builder = new StreamBuilder("builder_name")
.format("xml")
.xmlType(XmlType.NONE)
.parser(new XmlParserBuilder()
.indent()
)
.addRecord(Test.class);
Note the change to the XmlParserBuilder, to produce this output:
<?xml version="1.0" encoding="utf-8"?>
<test>
<field1>ABC</field1>
</test>

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.

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

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.

I want to do refactoring on my bad smell code using polymorphism. it's a removing 'if' statements issue

I'm an iOS app developer.
I got stuck on some refactoring issue while removing bad smells on my code.
it's like this.
My project uses a XML data,
<resource>
<item type='textbox' ... />
<item type='checkbox' ... />
<item type='selectbox' ... />
</resource>
and I use it by this code.
Item *item = nil;
for (Element *itemElement in resourceChilds)
{
...
if ([[itemElement valueForAttributeNamed:#"type"] isEqualToString:#"textbox"])
{
item = [[Textbox alloc] initWithProperty:itemAttributes];
...
}
else if ([[itemElement valueForAttributeNamed:#"type"] isEqualToString:#"checkbox"])
{
item = [[Checkbox alloc] initWithProperty:itemAttributes];
...
}
else if ([[itemElement valueForAttributeNamed:#"type"] isEqualToString:#"selectbox"])
{
item = [[Selectbox alloc] initWithProperty:itemAttributes];
...
}
...
}
'Item' class is the super class of 'Textbox', 'Checkbox' and 'Selectbox' classes.
And 'itemAttributes' object is an instance of NSDictionary.
As you can see above through 'initWithProperty:itemAttributes' I already gave the 'type' attribute value into an 'Item' instance. I think it's possible to use this information in the 'Item' instance to specialize it to Textbox or Checkbox or Selectbox.
Is there any way to remove these 'if' statements and refactoring this?
Any suggestion are welcome.
And I really appreciate it sharing your time for this.
Thanks,
MK
#anticyclope's suggestion is helpful. You don't necessary need to get rid of the ifs, but you can make the code a lot simpler. Here are some things you should do:
Extract the code within the for loop into its own method, something like itemForItemElement:.
Create a itemClassForItemElementType: method. This method will greatly simplify the implementation of itemForItemElement:, but still uses the ifs.
Optionally update the itemClassForItemElementType: to return the class using a NSDictionary. This isn't necessary, but might be what you'd do if you want the mapping to be dynamic, lets say if you create one in an external file. Here you'd use the code suggested in the link from #anticyclope.
A chain of ifs is something to avoid if you are duplicating the logic multiple places, but if you only do it once it is not so bad.
Be sure to have unit tests for all these changes.

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.