How do I reference array values within string.Format?
I am using XPath to exclude certain nodes within a menu. I want to expand on this to exclude nodes identified within an array.
This works to exclude all the nodes in the menu with id 2905 whose type is not content:
XmlNodeList nextLevelNodeList = currentNode
.SelectNodes(string
.Format("
Menu[not(MenuId = 2905)]
/Item[
ItemLevel = {0}
开发者_如何学Python and
ItemType != 'Javascript'
] |
Menu[MenuId = 2905]
/Item[
ItemLevel = {0}
and
ItemType = 'content'
]", iLevel));
What I'd like is to store the menuId and several others in an array and then reference that array within the string.Format function
Something like:
int[] excludeSubmenus = {2905, 323};
XmlNodeList nextLevelNodeList = currentNode
.SelectNodes(string
.Format("
Menu[not(MenuId in excludesubMenus)]
/Item[
ItemLevel={0}
and
ItemType != 'Javascript'
] |
Menu[MenuId in excludeSubMenus]
/Item[
ItemLevel={0}
and
ItemType='content'
]", iLevel));
Any advice would be greatly appreciated!
ta Nathan
Edit - include example xml
<Item>
<ItemId>322</ItemId>
<ItemType>Submenu</ItemType>
<ItemLevel>2</ItemLevel>
<Menu>
<MenuId>322</MenuId>
<MenuLevel>2</MenuLevel>
<Item>
<ItemId>2905</ItemId>
<ItemType>Submenu</ItemType>
<ItemLevel>3</ItemLevel>
<Menu>
<MenuId>2905</MenuId>
<MenuLevel>3</MenuLevel>
<Item>
<ItemId>19196</ItemId>
<ItemType>content</ItemType>
<ItemLevel>4</ItemLevel>
</Item>
<Item>
<ItemId>19192</ItemId>
<ItemType>Submenu</ItemType>
<ItemLevel>4</ItemLevel>
</Item>
</Menu>
</Item>
<Item>
<ItemId>2906</ItemId>
<ItemType>Submenu</ItemType>
<ItemLevel>3</ItemLevel>
<Menu>
<MenuId>323</MenuId>
<MenuLevel>3</MenuLevel>
<Item>
<ItemId>2432</ItemId>
<ItemType>content</ItemType>
<ItemLevel>4</ItemLevel>
</Item>
<Item>
<ItemId>12353</ItemId>
<ItemType>Submenu</ItemType>
<ItemLevel>4</ItemLevel>
</Item>
</Menu>
</Item>
</Menu>
</Item>
Use:
int[] excludeSubmenus = {2905, 323};
string notExpr = string.Empty;
for(int i=0; i < excludeSubmenus.Length; i++)
{
notExpr += string.Format("not(MenuId={0})", excludeSubmenus[i]);
if(i != excludeSubmenus.Count-1)
notExpr += " and ";
}
XmlNodeList nextLevelNodeList =
currentNode.SelectNodes(
string.Format("//Menu[MenuId in excludeSubMenus]/Item
[ItemLevel={1} and not(ItemType='Javascript')]",
notExpr, iLevel)
);
Do note: In the above code the strings have been split into different lines to enhance readability. In ypur code you must not split any string , or use the string +
(concatenation) operator to achieve the same effect.
I'm not 100% sure which nodes you expect here but based on what you tried to write, I tried to replicate in a scalable manner.
Basically you'll have to use the contents of the array to build up the right XPath. Here I have some functions to help change these exclusion lists into the appropriate XPath condition. Personally, I find using LINQ and string.Join()
to do this much easier to write and manage.
// supporting methods to build parts of the string
static string ElementNotInList<T>(string element, params T[] list)
{
return String.Join(" and ", list.Select(x => String.Concat(element, "!=", x)));
}
static string ElementInList<T>(string element, params T[] list)
{
return String.Join(" or ", list.Select(x => String.Concat(element, "=", x)));
}
var excludeSubmenus = new[] { 2905, 323 };
var xpath = String.Join("|",
String.Format("//Menu[{0}]/Item[ItemLevel={1} and ItemType!='Javascript']",
ElementNotInList("MenuId", excludeSubmenus), iLevel),
String.Format("//Menu[{0}]/Item[ItemLevel={1} and ItemType='content']",
ElementInList("MenuId", excludeSubmenus), iLevel)
);
var nextLevelNodeList = currentNode.SelectNodes(xpath);
Remember that you can always use this kind of XPath 1.0 expression for testing existence in sequence:
Menu/Item[
contains(
concat(' ','2905 323',' '),
concat(' ',../MenuId,' ')
) and
ItemType = 'content'
or
ItemType != 'Javascript'
][
ItemLevel = {0}
]
Thanks all for the advice - I'm leaving it as a simple xpath expression for now, have more important things to be doing. The chances of wanting to exclude more submenus are reasonably slim, and I'll deal with them as they arise...
I'm using the below as my solution -
XmlNodeList nextLevelNodeList = currentNode.SelectNodes(string.Format("Menu[not(MenuId = 2905) and not(MenuId = 323)]/Item[ItemLevel={0} and ItemType != 'Javascript'] | Menu[MenuId = 2905 or MenuId = 323]/Item[ItemLevel={0} and ItemType='content']", iLevel));
It's a pretty ordinary xml doc to start with, weighs in a 12.5 mb, and to be honest, I'd rather be working on other areas of the site....
Firstly, you don't need a "in" expression. The = operator automatically means "if it matches anything in the sequence."
As for your real question, if it's possible to reify the xpath expression as some sort of object and bind variables, then it's probably easy to do. I'm not familiar with c++ in partiucular, but I'd expect to see something vaguely like
XPathExpr foo = XPathExpr("/Item[not(type=$badType)]");` foo.bind("badType", toXpathSquence(the_bad_types));` foo.eval(the_xml_to_evaluate);`
very much like using variables in an sql prepared statement.
精彩评论