spring integration: multiple step multiple channel subscribers - spring-integration

I have the need to implement an integration flow made of multiple steps, where each step can be performed by a variable number of processors (plugins).
What I have so far:
<!-- gateway -->
<int:gateway default-request-channel="step/1" service-interface="ServiceGateway">
<int:method name="send" />
</int:gateway>
<!-- plugin 1 -->
<int:publish-subscribe-channel id="step/1" apply-sequence="true" />
<int:service-activator input-channel="step/1" output-channel="step/2">
<bean class="Transformer" />
</int:service-activator>
<int:service-activator input-channel="step/1" output-channel="step/2">
<bean class="Transformer2" />
</int:service-activator>
<!-- plugin 2 -->
<int:publish-subscribe-channel id="step/2" apply-sequence="true" />
<int:service-activator input-channel="step/2" output-channel="end">
<bean class="Transformer3" />
</int:service-activator>
<int:service-activator input-channel="step/2" output-channel="end">
<bean class="HttpTransformer4" />
</int:service-activator>
<!-- aggregation -->
<int:channel id="end" />
<int:aggregator input-channel="end" />
The expected behaviour is the following:
send the first request through the gateway
the input is processed by 2 "step/1" plugins
each output of the "step/1" plugins is processed by the "step/2" plugins
the aggregator should aggregate 4 items (1 -> 2 -> 4)
Everything is working fine but the result is not the expected, I receive only 2 (random) items instead of 4.
I suppose the problem is that the aggregator triggers the release after only two items because the "apply-sequence" in the "step/2" channel overwrites the "apply-sequence" in "step/1". So the question is: how can I make the aggregator wait for all the messages?
Thank you in advance.
Custom Release Strategy:
#SuppressWarnings("unchecked")
#Override
public boolean canRelease ( MessageGroup group ) {
MessageHeaders headers = group.getOne ().getHeaders ();
List<List<Object>> sequenceDetails = (List<List<Object>>) headers.get ( "sequenceDetails" );
System.out.println ( sequenceDetails );
int expectedSize = 1;
//map message id, max group size reached (i.e. sequenceNumber==sequenceSize)
for ( List<Object> sequenceItem : sequenceDetails ) {
if ( sequenceItem.get ( 1 ) != sequenceItem.get ( 2 ) ) {
System.err.println ( "--> AGG: no release check, group max not reached" );
return false;
}
expectedSize *= (int) sequenceItem.get ( 2 );//multiplies the group sizes
}
int expectedSize2 = expectedSize * (int) headers.get ( "sequenceSize" );
int currentSize = group.getMessages ().size () * expectedSize;
System.err.println ( "--> AGG: " + expectedSize2 + " : " + currentSize );
boolean canRelease = expectedSize2 == currentSize;
if ( canRelease ) {
System.out.println ( "ok" );
}
return canRelease;
}
Prints out:
[[7099b583-55d4-87d3-4502-993f05bfb388, 1, 2]]
--> AGG: no release check, group max not reached
[[7099b583-55d4-87d3-4502-993f05bfb388, 1, 2]]
--> AGG: no release check, group max not reached
[[7099b583-55d4-87d3-4502-993f05bfb388, 2, 2]]
--> AGG: 4 : 2
[[7099b583-55d4-87d3-4502-993f05bfb388, 2, 2]]
--> AGG: 4 : 4
Aggregation code:
#Aggregator
public Object aggregate ( List<Message<?>> objects ) {
List<Object> res = new ArrayList<> ();
for ( Message<?> m : objects ) {
res.add ( m.getPayload () );
MessageHeaders headers2 = m.getHeaders ();
System.out.println ( headers2.get ( "history" ) );
}
return res;
}
Prints out:
gateway2,core-channel,(inner bean)#57018165,async/step/1,core-channel,(inner bean)#57018165,async/step/2,core-channel,(inner bean)#57018165,end2
gateway2,core-channel,(inner bean)#57018165,async/step/1,core-channel,(inner bean)#57018165,async/step/2,core-channel,(inner bean)#57018165,end2
[102, 202] --> final result list, expected to be made of 4 items

Use a custom release strategy. The correlation data from the first pubsub is pushed onto a stack in the sequenceDetails headers by the second pubsub.
EDIT
The problem is there are two groups; you need to correlate on the initial correlationId. Here's a pure SpEL solution; it might be safer to use custom correlation/release strategies to ensure the data is as expected (and use getOne() instead of the iterator)...
<int:aggregator input-channel="end2"
correlation-strategy-expression=
"headers['sequenceDetails'][0][0]"
release-strategy-expression=
"size() == iterator().next().headers['sequenceSize'] * iterator().next().headers['sequenceDetails'][0][2]" />

Related

Joomla component category develop

After i follow joomla "Developing an MVC Component" Document until step 15- "Adding ACL"
Link - https://docs.joomla.org/J3.x:Developing_an_MVC_Component/Adding_ACL
everything works,
but i didn't see anything about create frontend category view,so i add a new view "category",here is my code:
in admin fields folder,i create a hellocategory.php
most code same with helloword,just change
protected $type = 'HelloCategory';
/**
* Method to get a list of options for a list input.
*
* #return array An array of JHtml options.
*/
protected function getOptions()
{
$db = JFactory::getDBO();
$query = $db->getQuery(true);
// custom
$query->select('#__categories.id as id,#__categories.title as category,#__categories.extension as exten');
$query->from('#__categories');
$query->where($db->quoteName('extension') . ' LIKE '. $db->quote('com_helloworld'));
// end custom
$db->setQuery((string) $query);
$messages = $db->loadObjectList();
$options = array();
view.html.php
class HelloWorldViewCategory extends JViewLegacy
{
/**
* Display the Hello World view
*
* #param string $tpl The name of the template file to parse; automatically searches through the template paths.
*
* #return void
*/
function display($tpl = null)
{
// Assign data to the view
$category = $this->get('Item');
// Check for errors.
if (count($errors = $this->get('Errors')))
{
JLog::add(implode('<br />', $errors), JLog::WARNING, 'jerror');
return false;
}
// Display the view
parent::display($tpl);
}
}
default.xml
<layout title="Category">
<message>category</message>
</layout>
<fields
name="request"
addfieldpath="/administrator/components/com_helloworld/models/fields"
>
<fieldset name="request">
<field
name="id"
type="hellocategory"
label="COM_UNOFD_UNOFD_FIELD_GREETING_LABEL"
description="COM_UNOFD_UNOFD_FIELD_GREETING_DESC"
/>
</fieldset>
</fields>
default.php
<?php var_dump($category); ?>
nothing output,
is there anything about create category document or sample online?i tried google for solution four days,but still no idea, or the only way is get data from database?
Closed,
because i found Joomla stackExange,thanks!
the question move to
http://joomla.stackexchange.com/questions/17475/joomla-3-helloworld-component-category

How to get strength indicator in Primefaces Password component from backing bean

I want to get strength indicator from the xhtml, in the backing beans.
If the "indicator" said Weak, i will do some action.
Right now this is my xhtml code
<h:form id="changePasswordForm" >
<p:messages id="changePasswordMessages" />
<h:panelGrid columns="3" >
<h:outputText for="oldPassword" value="CurrentPassword" />
<p:password id="oldPassword" value="#{changePasswordBean.oldPassword}"
label="CurrentPassword" required="true" feedback="false" minLength="6" />
<p:message for="oldPassword" display="icon" />
<p:spacer height="4px" />
<p:spacer height="4px" />
<p:spacer height="4px" />
<h:outputText for="newPassword1" value="#{NewPassword}" />
<p:password id="newPassword1" value="#{changePasswordBean.newPassword1}"
label="NewPassword" required="true" feedback="true" minLength="6" match="newPassword2"/>
<p:message for="newPassword1" display="icon" />
<h:outputText for="newPassword2" value="#{ConfirmPassword}" />
<p:password id="newPassword2" value="#{changePasswordBean.newPassword2}"
label="ConfirmPassword" required="true" feedback="true" minLength="6" />
<p:message for="newPassword2" display="icon" />
</h:panelGrid>
<table style="border:0; width:100%;">
<tr>
<td colspan="2">
<p:separator style="margin:0;" />
</td>
</tr>
<tr>
<td class="input" style="width:50%;">
<p:commandButton value="#{Save}"
process=":changePasswordForm"
update=":changePasswordForm"
actionListener="#{changePasswordBean.save()
icon="ui-icon-disk" />
</td>
</tr>
</table>
</h:form>
The problem now is, i don't know how to get the "weak", or "strong" message from the UI in the backing beans.
somebody could help me?
i'm using JSF 2, and PrimeFaces 3.4.
BEHOLD
The translation of the JavaScript implementation to Java.
Source
public class PasswordValidator implements Serializable {
/**
* Less than this is weak, more that this is good.
*/
public final static int MEDIUM = 30;
/**
* More than this is strong.
*/
public final static int STRONG = 80;
private String password;
private int score;
public PasswordValidator() {
}
public PasswordValidator(String password) {
setPassword(password);
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
validatePassword();
}
private void validatePassword() {
score = testStrength(password);
}
public int getScore() {
return score;
}
public boolean isWeak() {
return score <= MEDIUM;
}
public boolean isAtLeastGood() {
return score >= MEDIUM;
}
public boolean isStrong() {
return score >= STRONG;
}
public boolean isSecure() {
return score == 100;
}
public static int testStrength(String d) {
if (d == null || d.isEmpty())
return 0;
//var b=0,c=0,a=this;
float b = 0;
int c;
//c=d.match("[0-9]");b+=a.normalize(c?c.length:1/4,1)*25;
c = countMatches(d, "[0-9]"); // asks for at least one number
b += normalize(c != 0 ? 1 : 1 / 4F, 1) * 25;
//c=d.match("[a-zA-Z]");b+=a.normalize(c?c.length:1/2,3)*10;
c = countMatches(d, "[a-zA-Z]"); // matches only latin characters, not other character sets
b += normalize(c != 0 ? 1 : 1 / 2F, 3) * 10;
//c=d.match("[!##$%^&*?_~.,;=]");b+=a.normalize(c?c.length:1/6,1)*35;
c = countMatches(d, "[!##$%^&*?_~.,;=]"); // asks for at least on symbol
b += normalize(c != 0 ? 1 : 1 / 6F, 1) * 35;
//c=d.match("[A-Z]");b+=a.normalize(c?c.length:1/6,1)*30;
c = countMatches(d, "[A-Z]"); // asks for at least one capital letter
b += normalize(c != 0 ? 1 : 1 / 6F, 1) * 30;
//b*=d.length/8;
b *= d.length() / 8F;
System.out.println(b);
//return b>100?100:b
return b > 100 ? 100 : (int) b;
}
private static float normalize(float a, float c) {
return a - c <= 0 ? a / c : 1 + 0.5F * (a / (a + c / 4));
}
private static int countMatches(String container, String regex) {
int i = 0;
Matcher m = Pattern.compile(regex).matcher(container);
while (m.find())
i++;
return i;
}
}
Usage
PasswordValidator.testStrength("password"); // e.g. 83
// or
PasswordValidator pv = new PasswordValidator(); // or new PasswordValidator("password")
pv.setPassword("password");
pv.getScore(); // e.g. 83
pv.isAtLeastGood(); // e.g. true
pv.isStrong(); // e.g. true
Tests
Results of password strength score from the JavaScript/PrimeFaces implementation and mine.
Password My class PrimeFaces
123456 28 28.125
Ofar-E*Qnmcm_eSPA 100 100
123456789aZ 88 88.22916666666666
2010.11.02 83 83.33333333333334
mississDOGippi 79 79.47916666666666
Works Perfect!!!
Notes
The PrimeFaces password component considers a password consisting of 31 letters (without capitals, numbers or symbols) to be a strong password, which is not true.
To test PrimeFaces component execute PrimeFaces.widget.Password.prototype.testStrength(PF('password-widget-var').jq.val())
To get the code of the javascript function execute PrimeFaces.widget.Password.prototype.testStrength and PrimeFaces.widget.Password.prototype.normalize
Source code of PrimeFaces.widget.Password.prototype.testStrength
function (d){
var b=0, c=0, a=this;
c=d.match("[0-9]"); b+=a.normalize(c?c.length:1/4,1)*25;
c=d.match("[a-zA-Z]"); b+=a.normalize(c?c.length:1/2,3)*10;
c=d.match("[!##$%^&*?_~.,;=]"); b+=a.normalize(c?c.length:1/6,1)*35;
c=d.match("[A-Z]"); b+=a.normalize(c?c.length:1/6,1)*30;
b*=d.length/8;
return b>100?100:b
}
Source code of PrimeFaces.widget.Password.prototype.normalize
function (a,c){var b=a-c;if(b<=0){return a/c}else{return 1+0.5*(a/(a+c/4))}}
There is no reason to use float type in Java for score so I used integer.
Both implementations accept only latin character sets.
(again) Works Perfect!!!
So, all you have to do in the back-end is pass the password to PasswordValidator and you will get its strength, which will be the same value as the one calculated by PrimeFaces. To calculate if the password is weak, good or strong as calculated by PrimeFaces use the corresponding methods of the PasswordValidator.

Failed to send update request to backup node

Gridgain version: 6.5.5
Cache mode: replaced
Cache K-V type: [Integer, Integer []]
Node startup mode: CMD ggstart -v -i
Problem description: When I'm start only one node, it can calculate correctly and the result is right, however, when I'm start multiple node with the example, it doesn't work and the exception information as follows.
SampleComputeNumTask.java:
#Override
public Collection<CbJob<Long, Integer[]>> splitJob(Serializable... args) throws CJobExcuteException {
Collection<CbJob<Long, Integer[]>> list = new ArrayList<CbJob<Long, Integer[]>>();
loadCache(args);
GridCache<Integer, Integer[]> cache = grid.cache("share-hqfx");
for (GridCacheEntry<Integer, Integer[]> entry : cache.entrySet()) {
list.add(new CbJob<Long, Integer[]>(entry.getKey(), new MyCall(), entry.getValue()));
}
return list;
}
#Override
public void loadCache(Serializable... args) throws CJobExcuteException {
GridCache<Integer, Integer[]> cache = grid.cache("share-hqfx");
try {
cache.globalClearAll(0);
} catch (GridException e1) {
e1.printStackTrace();
}
int j = 0;
int a = 0;
int len = args == null ? 1000 : (Integer) args[0];
Integer[] arg = new Integer[len];
for (int i = 1; i < 1000000; i++) {
arg[a] = i;
a++;
if (a == 1000 || i == 1000000) {
++j;
try {
cache.putx(j, arg);
} catch (GridException e) {
e.printStackTrace();
}
a = 0;
arg = new Integer[1000];
}
}
}
Exception trace:
[16:02:03,880][ERROR][gridgain-#60%rest-null%][GridDhtAtomicUpdateFuture] Failed to send update request to backup node (did node leave the grid?): 5e1b14d3-eb17-499d-9f0e-0de9a4a00c90
class org.gridgain.grid.GridException: Failed to deploy class for local deployment [clsName=java.lang.Integer, ldr=GridUriDeploymentClassLoader [urls=[file:/F:/gridgain-fabric-ent/tmp/grid/gg.uri.deployment.tmp/41ed7058-4555-47d7-83e2-773436b2d12e/dirzip_SampleComputeTask5391357847454873740.gar/]]]
For more information see:
Troubleshooting: http://bit.ly/GridGain-Troubleshooting
Documentation Center: http://bit.ly/GridGain-Documentation
at org.gridgain.grid.kernal.processors.cache.GridCacheDeploymentManager.registerClass(GridCacheDeploymentManager.java:624)
at org.gridgain.grid.kernal.processors.cache.GridCacheDeploymentManager.registerClass(GridCacheDeploymentManager.java:562)
at org.gridgain.grid.kernal.processors.cache.GridCacheMessage.prepareObject(GridCacheMessage.java:181)
at org.gridgain.grid.kernal.processors.cache.GridCacheMessage.marshalCollection(GridCacheMessage.java:478)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicUpdateRequest.prepareMarshal(GridDhtAtomicUpdateRequest.java:576)
at org.gridgain.grid.kernal.processors.cache.GridCacheIoManager.onSend(GridCacheIoManager.java:334)
at org.gridgain.grid.kernal.processors.cache.GridCacheIoManager.send(GridCacheIoManager.java:362)
at org.gridgain.grid.kernal.processors.cache.GridCacheIoManager.send(GridCacheIoManager.java:350)
at org.gridgain.grid.kernal.processors.cache.GridCacheIoManager.send(GridCacheIoManager.java:557)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicUpdateFuture.map(GridDhtAtomicUpdateFuture.java:324)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.updateAllAsyncInternal0(GridDhtAtomicCache.java:1005)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.updateAllAsyncInternal(GridDhtAtomicCache.java:854)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridNearAtomicUpdateFuture.mapSingle(GridNearAtomicUpdateFuture.java:740)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridNearAtomicUpdateFuture.map0(GridNearAtomicUpdateFuture.java:581)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridNearAtomicUpdateFuture.mapOnTopology(GridNearAtomicUpdateFuture.java:454)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridNearAtomicUpdateFuture.map(GridNearAtomicUpdateFuture.java:319)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache$13.apply(GridDhtAtomicCache.java:662)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache$13.apply(GridDhtAtomicCache.java:660)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.asyncOp(GridDhtAtomicCache.java:584)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.updateAllAsync0(GridDhtAtomicCache.java:660)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.putxAsync(GridDhtAtomicCache.java:343)
at org.gridgain.grid.kernal.processors.cache.GridCacheAdapter.putxAsync(GridCacheAdapter.java:2284)
at org.gridgain.grid.kernal.processors.cache.distributed.dht.atomic.GridDhtAtomicCache.putx(GridDhtAtomicCache.java:329)
at org.gridgain.grid.kernal.processors.cache.GridCacheProxyImpl.putx(GridCacheProxyImpl.java:705)
at com.topgether.cbgrid.datagrid.SampleComputeNumTask.loadCache(SampleComputeNumTask.java:64)
at com.topgether.cbgrid.datagrid.SampleComputeNumTask.splitJob(SampleComputeNumTask.java:38)
at com.topgether.cbgrid.datagrid.CGridTask.split(CGridTask.java:53)
at com.topgether.cbgrid.datagrid.CGridTask.split(CGridTask.java:1)
at org.gridgain.grid.compute.GridComputeTaskSplitAdapter.map(GridComputeTaskSplitAdapter.java:111)
at org.gridgain.grid.kernal.processors.task.GridTaskWorker$2.call(GridTaskWorker.java:404)
at org.gridgain.grid.kernal.processors.task.GridTaskWorker$2.call(GridTaskWorker.java:402)
at org.gridgain.grid.util.GridUtils.wrapThreadLoader(GridUtils.java:6078)
at org.gridgain.grid.kernal.processors.task.GridTaskWorker.body(GridTaskWorker.java:401)
at org.gridgain.grid.util.worker.GridWorker.run(GridWorker.java:151)
at org.gridgain.grid.kernal.processors.task.GridTaskProcessor.startTask(GridTaskProcessor.java:607)
at org.gridgain.grid.kernal.processors.task.GridTaskProcessor.execute(GridTaskProcessor.java:365)
at org.gridgain.grid.kernal.GridComputeImpl.execute(GridComputeImpl.java:122)
at org.gridgain.grid.kernal.processors.rest.handlers.task.GridTaskCommandHandler.handleAsyncUnsafe(GridTaskCommandHandler.java:191)
at org.gridgain.grid.kernal.processors.rest.handlers.task.GridTaskCommandHandler.handleAsync(GridTaskCommandHandler.java:138)
at org.gridgain.grid.kernal.processors.rest.GridRestProcessor.handleRequest(GridRestProcessor.java:203)
at org.gridgain.grid.kernal.processors.rest.GridRestProcessor.access$100(GridRestProcessor.java:61)
at org.gridgain.grid.kernal.processors.rest.GridRestProcessor$2.body(GridRestProcessor.java:109)
at org.gridgain.grid.util.worker.GridWorker.run(GridWorker.java:151)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Configuration:
<bean id="grid.cfg" class="org.gridgain.grid.GridConfiguration">
<!-- Cache configurations. -->
<property name="cacheConfiguration">
<list>
<bean class="org.gridgain.grid.cache.GridCacheConfiguration">
<property name="name" value="share-hqfx" />
<property name="cacheMode" value="REPLICATED" />
<property name="queryIndexEnabled" value="true" />
</bean>
</list>
</property>
<property name="restJettyPath" value="#{ systemEnvironment['GRIDGAIN_HOME'] }/config/jetty-config.xml" />
<property name="deploymentSpi">
<bean class="org.gridgain.grid.spi.deployment.uri.GridUriDeploymentSpi">
<property name="temporaryDirectoryPath" value="#{ systemEnvironment['GRIDGAIN_HOME'] }/tmp/grid" />
</bean>
</property>
<property name="discoverySpi">
<bean class="org.gridgain.grid.spi.discovery.tcp.GridTcpDiscoverySpi">
<property name="ipFinder">
<bean class="org.gridgain.grid.spi.discovery.tcp.ipfinder.vm.GridTcpDiscoveryVmIpFinder">
<property name="addresses">
<list>
<!-- In distributed environment, replace with actual host IP address. -->
<value>127.0.0.1:47500..47509</value>
</list>
</property>
</bean>
</property>
</bean>
</property>
<property name="deploymentMode" value="CONTINUOUS" />
</bean>

Change XML tag name

I want to transform an XML document which I have parsed with XmlSlurper. The (identical) XML tag names should be replaced with the value of the id attribute; all other attributes should be dropped. Starting from this code:
def xml = """<tag id="root">
| <tag id="foo" other="blah" more="meh">
| <tag id="bar" other="huh"/>
| </tag>
|</tag>""".stripMargin()
def root = new XmlSlurper().parseText(xml)
// Some magic here.
println groovy.xml.XmlUtil.serialize(root)
I want to get the following:
<root>
<foo>
<bar/>
</foo>
</root>
(I write test assertions on the XML, and want to simplify the structure for them.) I've read Updating XML with XmlSlurper and searched around, but found no way with replaceNode() or replaceBody() to exchange a node while keeping its children.
Adding the 'magic' in to the code in the question gives:
def xml = """<tag id="root">
| <tag id="foo" other="blah" more="meh">
| <tag id="bar" other="huh"/>
| </tag>
|</tag>""".stripMargin()
def root = new XmlSlurper().parseText(xml)
root.breadthFirst().each { n ->
n.replaceNode {
"${n.#id}"( n.children() )
}
}
println groovy.xml.XmlUtil.serialize(root)
Which prints:
<?xml version="1.0" encoding="UTF-8"?><root>
<foo>
<bar/>
</foo>
</root>
HOWEVER, this will drop any content in the nodes. To maintain content, we would probably need to use recursion and XmlParser to generate a new doc from the existing one... I'll have a think
More general solution
I think this is more generalised:
import groovy.xml.*
def xml = """<tag id="root">
| <tag id="foo" other="blah" more="meh">
| <tag id="bar" other="huh">
| something
| </tag>
| <tag id="bar" other="huh">
| something else
| </tag>
| <noid>woo</noid>
| </tag>
|</tag>""".stripMargin()
def root = new XmlParser().parseText( xml )
def munge( builder, node ) {
if( node instanceof Node && node.children() ) {
builder."${node.#id ?: node.name()}" {
node.children().each {
munge( builder, it )
}
}
}
else {
if( node instanceof Node ) {
"${node.#id ?: node.name()}"()
}
else {
builder.mkp.yield node
}
}
}
def w = new StringWriter()
def builder = new MarkupBuilder( w )
munge( builder, root )
println XmlUtil.serialize( w.toString() )
And prints:
<?xml version="1.0" encoding="UTF-8"?><root>
<foo>
<bar>something</bar>
<bar>something else</bar>
<noid>woo</noid>
</foo>
</root>
Now passes through nodes with no (or empty) id attributes

how to update the second element's attribute on XML?

May i know how to update the second element's attribute using linq to xml? I do write some code but it doesnt work, it only update the user attribute....I'm sorry for asking this kind simple question.
My XML:
<Settings>
<Settig>
<User id="1" username="Aplha"/>
<Location Nation="USA" State="Miami" />
<Email>user1#hotmail.com</Email>
</Setting>
</Settings>
My Cs :
public static void saveSetting(MainWindow main)
{
XDocument document = XDocument.Load("Setting.xml");
IEnumerable<XElement> query = from p in document.Descendants("User")
where p.Attribute("id").Value == "1"
select p;
foreach (XElement element in query)
{
string i = "New York";
element.SetAttributeValue("State", i);
}
document.Save("Setting.xml");
}
You want to select the Setting elements; you can still select on id=1, like this:
IEnumerable<XElement> query = from p in document.Descendants("Setting")
where p.Element("User").Attribute("id").Value == "1"
select p;
Then select the Location element before updating:
foreach (XElement element in query)
{
element.Element("Location").SetAttributeValue("State", "New York");
}

Resources