How can I set the inner HTML in a QDomElement?
When I’m using QWebElement I have the method QWebElement::setInnerXml, there is some similar method in QDomElement?
There's no API to "inject" XML snippets as text into a QDomDocument (or QXmlStreamWriter). One has to use the API and create the nodes programmatically.
Assuming you have a string to start with, my current solution is to generate a DOM tree from it, import that tree in a fragment, then copy those in the right place (you must have an import which is why you need an intermediate fragment. Quite unfortunate if you ask me.)
// assuming that 'n' is the node you are replacing or at least inserting after
// 'parent' is the parent of 'n'
// 'result' is the string to replace 'n' or insert after 'n'
QDomDocument doc_text("snap");
doc_text.setContent("<text>" + result + "</text>", true, NULL, NULL, NULL);
QDomDocumentFragment frag(xml.createDocumentFragment());
frag.appendChild(xml.importNode(doc_text.documentElement(), true));
QDomNodeList children(frag.firstChild().childNodes());
const int max(children.size());
QDomNode previous(n);
for(int i(0); i < max; ++i)
{
QDomNode l(children.at(0));
parent.insertAfter(children.at(0), previous);
previous = l;
}
// if you are replacing, then delete node n as well
parent.removeChild(n);
Note that the <text> tag is used so that way result does not need to be a tag, it could just be text and it will still work.
Obviously, if you have a fragment or XML from another document to start with, ignore the code that creates that code in the doc_text object.
Related
This pertains to .NET Web Performance Tests.
If I have an ASP.NET page with a GridView that has a column of ints, how do I write an extraction rule to get the largest int in the column?
I tried creating a custom extraction rule by inheriting from ExtractionRule and in the Extract method using e.Response.HtmlDocument.GetFilteredHtmlTags however, the HtmlTags returned don't seem to expose their innerHtml contents.
Perhaps you can write an extraction rule that gets the whole column, then process the numbers to get their maximum value. Alternatively, use a built-in extraction rule to get the whole column, then write a plugin to get the maximum value. In either case your code should expect a mixture of numbers and other text.
Ben Day has a great blog post containing two types that express similar concerns. TableColumnValueValidator and ExtractRandomValueFromTable.
http://www.benday.com/2013/08/19/validation-extraction-rules-for-visual-studio-2012-web-performance-tests/
In the Extract(object, ExtractionEventArgs), you need to parse the ExtractionEventArgs.Response.BodyString. Ben uses the HtmlAgilityPack library for this. http://www.nuget.org/packages/htmlagilitypack
Something like this is roughly the code you'd need. This is simliar logic to ExtractRandomValueFromTable.
This does not account for thead/tbody or cells that span multiple columns/rows.
HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(e.Response.BodyString);
HtmlNode table = doc.GetElementbyId(TableId); // TableId is a test property
HtmlNodeCollection columns = table.SelectNodes("//th");
int columnIndex = FindColumnIndexByName(columns, ColumnName); // ColumnName is a test property
HtmlNodeCollection rows = table.SelectNodes("//tr")
int maxValue = Int32.MinValue;
foreach(HtmlNode row in rows)
{
HtmlNodeCollection cells = row.SelectNodes("./td");
// Todo check for bounds of cells here
HtmlNode cell = cells[columnIndex];
int value = Int32.MinValue;
Int32.TryParse(cell.InnerText.Trim(), out value);
maxValue = Math.Max(value, maxValue);
}
e.WebTest.Context.Add(ContextParameterName, maxValue);
int FindColumnIndexByName(HtmlNodeCollection columns, string columnName)
{
for(int i=0; i<columns.Count; i++)
if (String.Equals(columns[i].InnerText, columnName, StringComparison.OrdinalIgnoreCase))
{
return i;
}
return -1;
}
Picture this: I have a JSON file which has a list of Node objects that contain a list of Link types. Something like:
node1:
links: node1,node2
node3,node1
node1,node6
node2:
links: node2,node1
node2,node9
node7,node2
I need to identify unique pairs of links - ie a (node_a,node_b) pair. Note that (node_b,node_a) represents the same thing.
The Link class has getter methods that return a pointer to either the source/destination Node. In the file, the information about the links is stored as a string that has the source node's name and the destination node name, like: (source,destination).
When I build my structure from file, I first create the Nodes and only then I create the Links. The Link constructor is as follows:
Link::Link(Node *fromNode, Node *toNode)
And my code to create the links:
QList<Link*> consumedLinks; // list where I was trying to place all the non-duplicate links
// for each node in the file
foreach(QVariant nodesMap, allNodesList){
QVariantMap node1 = nodesMap.toMap();
QList<QVariant> nodeDetails = node1["node"].toList();
QVariantMap allLinksMap = nodeDetails.at(9).toMap();
// extract all the links of the node to a list of QVariant
QList<QVariant> linksList = allLinksMap["links"].toList();
// for each Link in that list
foreach(QVariant linkMap, linksList){
QVariantMap details = linkMap.toMap();
Node *savedFromNode;
Node *savedToNode;
// get all the Items in the scene
QList<QGraphicsItem*> itemList = scene->items();
// cast each item to a Node
foreach(QGraphicsItem *item, itemList){
Node* tempNode = qgraphicsitem_cast<Node*>(item);
if(tempNode){
// check what the node name matches in the link list
if(tempNode->text()==details["fromNode"]){
savedFromNode = tempNode;
}
if(tempNode->text()==details["toNode"]){
savedToNode = tempNode;
}
}
}
// create the link
Link *linkToCheck = new Link(savedFromNode,savedToNode);
// add it to the links list (even if duplicate)
consumedLinks.append(linkToCheck);
}
}
// add all the links as graphics items in the scene
foreach(Link *linkToCheck, consumedLinks){
scene->addItem(linkToCheck);
}
So right now this doesn't check for duplicates in the consumedLinks list (obviously). Any ideas on how to achieve this?
NOTE: I know that the pseudo-JSON above isn't valid, it's just to give you an idea of the structure.
NOTE2: I rephrased and added detail and code to the question to make it clearer to understand what I need.
1. Normalize links i.e. replace (a, b) to (b, a) when b < a. Each link should be represented as (a, b), a < b.
2. Create a QSet< QPair<Node*, Node *> > variable and put all links into it. Each link object can be maked using qMakePair(node1, node2). Since QSet is an qnique container, all duplicates will be removed automatically.
Well, I didn't exactly do the same as #Riateche suggested, but something similar.
I basically created a QList<QPair<QString,QString> > so I could check for duplicates easily and then I created a list of Link based on the items in the QPair list. Something like:
QList<Node*> existingNodes;
QList<QPair<QString,QString> > linkPairList;
QList<QGraphicsItem*> itemList = scene->items();
foreach(QGraphicsItem *item, itemList){
Node* tempNode = qgraphicsitem_cast<Node*>(item);
if(tempNode){
existingNodes.append(tempNode);
}
}
foreach(QVariant nodesMap, allNodesList){
QVariantMap node1 = nodesMap.toMap();
QList<QVariant> nodeDetails = node1["node"].toList();
QVariantMap allLinksMap = nodeDetails.at(9).toMap();
QList<QVariant> linksList = allLinksMap["links"].toList();
foreach(QVariant linkMap, linksList){
QVariantMap details = linkMap.toMap();
QPair<QString,QString>linkPair(details["fromNode"].toString(),details["toNode"].toString());
QPair<QString,QString>reversedLinkPair(details["toNode"].toString(),details["fromNode"].toString());
if(!linkPairList.contains(linkPair)){
// !dupe
if(!linkPairList.contains(reversedLinkPair)){
// !reversed dupe
linkPairList.append(linkPair);
}
}
qDebug()<<"number of pairs: "<<linkPairList.size();
}
}
QPair<QString,QString> linkPairInList;
foreach(linkPairInList, linkPairList){
Node *savedFromNode;
Node *savedToNode;
foreach(Node* node, existingNodes){
if(node->text()==linkPairInList.first){
savedFromNode=node;
}
if(node->text()==linkPairInList.second){
savedToNode=node;
}
}
Link *newLink = new Link(savedFromNode,savedToNode, "input");
scene->addItem(newLink);
}
This is the correct answer because it solves what I needed to solve. Since I only got there because of #Riateche's comments, I +1'd his answer.
I have simple Qt GUI application which uses QtWebkit. I am loading complex page with a lot of nested IFRAME-tags. And I want to traverse complete DOM tree (like Chrome browser does in debug-panel) including content of iframes.
My code is:
QWebElement doc = ui->webView->page()->mainFrame()->documentElement();
QWebElement iframe = doc.findFirst("iframe[id=someid]");
QWebFrame *webFrame = ....? // how to get the QWebFrame for an iframe/frame QWebElement?
Note:
I can traverse over all frames (including nested):
void MainWindow::renderFramesTree(QWebFrame *frame, int indent)
{
QString s;
s.fill(' ', indent * 4);
ui->textLog->appendPlainText(s + " " + frame->frameName());
foreach (QWebFrame *child, frame->childFrames())
renderFramesTree(child, indent + 1);
}
But my question is not about that. I need to get corresponding QWebFrame* of iframe-QWebElement.
Thanx!
Each QWebFrame has QList<QWebFrame *> QWebFrame::childFrames () const method. Each frame has also QString QWebFrame::frameName () const. Combining both may let you find what you need.
QWebFrame * frameImLookingFor = NULL;
foreach(QWebFrame * frame, ui->webView->page()->mainFrame()->childFrames())
{
if (frame->frameName() == QLatin1String("appFrame"))
{
frameImLookingFor = frame;
break;
}
}
if (frameImLookingFor)
{
// do what you need
}
run selector on current frame
if it returns something that's good we found that our webframe is on the current frame, lets call this element X :
now get all frame elements using selector "iframe,frame" from current page
loop trough all those elements and try to match them with X frame
this way you'll be able to find exact INDEX of the X frame in document
finally you are now able to focus on child frame with that INDEX
else this means that selector is not found in this frame
loop trough all child frames and repeat the whole process for each child frame
I'm using libxml2 to read/write xml files. Now I'm trying to write a CDATA node.
Here is what I tried:
nodePtr = xmlNewChild( parentPtr, NULL, "foo", NULL );
xmlNodeSetContentLen( nodePtr, "<![CDATA[\nTesting 1 < 2\n]]>", len );
However, this results in the following encoded text:
<foo><![CDATA[
Testing 1 < 2
]]></foo>
I'm thinking that perhaps there might be a CDATA-specific libxml2 API. Or maybe I have to call something else to tell libxml2 not to automatically encode the node content?
Figured it out. The trick is in knowing that CDATA text content is actually a child and not a part of the current node, and the critical API to call is xmlNewCDataBlock(). Using the same example as above:
nodePtr = xmlNewChild( parentPtr, NULL, "foo", NULL );
cdataPtr = xmlNewCDataBlock( doc, "Testing 1 < 2", 13 );
xmlAddChild( nodePtr, cdataPtr );
This will produce the following xml:
<foo><![CDATA[Testing 1 < 2]]></foo>
I cannot say for all versions of libxml2, but according to libxml2-2.9.4 the doc part of returning node of xmlNewChild comes from its parent. Also the parent of child node returned from xmlNewCDataBlock is set by doc parameter. So the following would be a good practice:
const char str[] = "said the kitty";
xmlNodePtr node = xmlNewNode(NULL, BAD_CAST "meow");
xmlNodePtr cdata_node = xmlNewCDataBlock(node->doc, BAD_CAST str, strlen(str));
xmlAddChild(node, cdata_node);
The resulting xml is
<meow><![CDATA[said the kitty]]></meow>
And it would not matter if node is part of an xmlDoc or not
i have a problem where i want to remove the last character from a textfield (including linebreaks) that has multiple textformats without removing the formats.
so far i have:
textfield.replaceText(textField.length-1,textField.length-1,'');
i guess this doesn't remove linebreaks, and is very slow, seems to destroy my textformats.
or:
textfield.text = textfield.text.slice(0,-1);
this is faster but removes all textformats as well.
It is a bit tedious, but you can use the htmlText-property of TextField, even though you are not formatting your text with StyleSheets: Flash will transform all your formatting information into HTML text internally, so even though you set textField.text, you can still get xml formatted text to work with:
textField.text = "A test.";
trace (textField.htmlText);
will actually return:
<P ALIGN="LEFT"><FONT FACE="Times Roman" SIZE="12" COLOR="#000000" LETTERSPACING="0" KERNING="0">A test.</FONT></P>
Text will always appear within <FONT> tags reflecting the changes you made using setTextFormat(). You can, therefore, iterate over the XML contained in this line, and remove only the last character in the last TextNode:
private function removeLastCharacter (textField:TextField) : void {
var xml:XML = new XML (textField.htmlText);
for ( var i : int = xml.children().length()-1; i >= 0; i-- ){
var node:XML = xml.children()[i];
if ( node.name() == "FONT") {
var tx:String = node.text()[0].toString();
node.setChildren (tx.substr (0, tx.length-1));
break;
}
}
textField.htmlText = xml;
trace (textField.text); // In the above example, output will be: "A test";
}
I hope I understand your problem correctly. If you keep your formatting in htmlText, I have one possible solution:
The idea is to keep the formatted text in an XML format, and modify the XML. XML will keep your formatting intact, you don't have to do string aerobatics to maintain them. The downsides are of course having to keep the formatting XML valid, and the extra variable.
Here's an example:
var tf:TextField = new TextField();
var t:XML = new XML("<html><p>lalala</p><font color='#ff0000'> lol</font></html>");
tf.htmlText = t.toXMLString();
t.font[0] = t.font[0].text().slice(0, -1);
tf.htmlText = t.toXMLString();
addChild(tf);