How to retrieve an internal text - html

How to retrieve text from this template using XPath?
<div class="c">
<span> a </span>
text
</div>
I know that //div[#class='c']//text() returns whole div part, but I only want the text.

There is one slash too much. One slash makes sure that only text directly below div is returned:
//div[#class='c']/text()
The above returns text nodes. In many places in XPath or XQuery, they get automatically converted to strings (atomized), but you can also explicitly force a conversion to strings:
//div[#class='c']/text()/string()
or if you need to clean up for spaces and empty text nodes to return exactly text:
XPath 2.0:
//div[#class='c']/text()/normalize-space()[string-length() gt 0]
XPath 1.0 (for this specific document):
normalize-space(//div[#class='c']/text()[2])

Related

How to match text and skip HTML tags using a regular expression?

I have a bunch of records in a QuickBase table that contain a rich text field. In other words, they each contain some paragraphs of text intermingled with HTML tags like <p>, <strong>, etc.
I need to migrate the records to a new table where the corresponding field is a plain text field. For this, I would like to strip out all HTML tags and leave only the text in the field values.
For example, from the below input, I would expect to extract just a small example link to a webpage:
<p>just a small <a href="#">
example</a> link</p><p>to a webpage</p>
As I am trying to get this done quickly and without coding or using an external tool, I am constrained to using Quickbase Pipelines' Text channel tool. The way it works is that I define a regex pattern and it outputs only the bits that match the pattern.
So far I've been able to come up with this regular expression (Python-flavored as QB's backend is written in Python) that correctly does the exact opposite of what I need. I.e. it matches only the HTML tags:
/(<[^>]*>)/
In a sense, I need the negative image of this expression but have not be able to build it myself.
Your help in "negating" the above expression is most appreciated.
Assuming there are no < or > elsewhere or entity-encoded, an idea using a lookbehind.
(?:(?<=>)|^)[^<]+
See this demo at regex101
(?:(?<=>)|^) is an alternation between either ^ start of the string or looking behind for any >. From there [^<]+ matches one or more characters that are not < (negated character class).

XPath to return string concatenation splitted by html tag

How can I return string value containing the concatenated values using an XPath expression?
<div>
This text node (1) should be returned.
<em>And the value of this element.</em>
And this.
</div>
<div>
This text node (2) should be returned.
And this.
</div>
<div>
This text node (3) should be returned.
<em>And the value of this element.</em>
And this.
</div>
The returned value should be an array of strings split by div element:
"This text node (1) should be returned. And the value of this element. And this."
"This text node (2) should be returned. And this."
"This text node (3) should be returned. And the value of this element. And this."
Is this possible in a single XPath expression?
XPath 1.0
Cannot do with pure XPath 1.0. Instead, select the div elements:
//div
and then apply space normalization of the string values of each div element in the language hosting the XPath library call.
XPath 2.0
This XPath 2.0 expression,
//div/normalize-space()
will return the normalized string value of all div elements in the document:
This text node (1) should be returned. And the value of this element. And this.
This text node (2) should be returned. And this.
This text node (3) should be returned. And the value of this element. And this.
as requested.

Why won't my XPath select link/button based on its label text?

<a href="javascript:void(0)" title="home">
<span class="menu_icon">Maybe more text here</span>
Home
</a>
So for above code when I write //a as XPath, it gets highlighted, but when I write //a[contains(text(), 'Home')], it is not getting highlighted. I think this is simple and should have worked.
Where's my mistake?
Other answers have missed the actual problem here:
Yes, you could match on #title instead, but that's not why OP's
XPath is failing where it may have worked previously.
Yes, XML and XPath are case sensitive, so Home is not the same as
home, but there is a Home text node as a child of a, so OP is
right to use Home if he doesn't trust #title to be present.
Real Problem
OP's XPath,
//a[contains(text(), 'Home')]
says to select all a elements whose first text node contains the substring Home. Yet, the first text node contains nothing but whitespace.
Explanation: text() selects all child text nodes of the context node, a. When contains() is given multiple nodes as its first argument, it takes the string value of the first node, but Home appears in the second text node, not the first.
Instead, OP should use this XPath,
//a[text()[contains(., 'Home')]]
which says to select all a elements with any text child whose string value contains the substring Home.
If there weren't surrounding whitespace, this XPath could be used to test for equality rather than substring containment:
//a[text()[.='Home']]
Or, with surrounding whitespace, this XPath could be used to trim it away:
//a[text()[normalize-space()= 'Home']]
See also:
Testing text() nodes vs string values in XPath
Why is XPath unclean constructed? Why is text() not needed in predicate?
XPath: difference between dot and text()
yes you are doing 2 mistakes, you're writing Home with an uppercase H when you want to match home with a lowercase h. also you're trying to check the text content, when you want to check check the "title" attribute. correct those 2, and you get:
//a[contains(#title, 'home')]
however, if you want to match the exact string home, instead of any a that has home anywhere in the title attribute, use #zsbappa's code.
You can try this XPath..Its just select element by attribute
//a[#title,'home']

How can you view the output XPATH functions like normalize-space()?

Say I have the following HTML:
<div class="instruction" id="scan-prompt">
<span class="long instruction">Scan </span>
<span id="slot-to-scan">A-2</span>
<span class="long instruction"> to prep</span>
</div>
And I'm trying to write an XPATH selector like this
//div[#id='scan-prompt' and normalize-space()='Scan A-2 to prep']
Is there a way to see what the normalize-space output actually is?
I know you can do $x("//div[#id='scan-prompt']) in chrome debugger but I don't know how to go from that to seeing the output of normalize-space.
Why can you not simply use the path expression
normalize-space(//div[#id='scan-prompt'])
to see what the normalized string value would look like? Other than that, what normalize-space() does exactly is:
Removing any leading or trailing whitespaces from the string argument
Collapsing any sequence of whitespace characters to just one whitespace character
If handed an element node as an argument (as is the case with your original expression), the function evaluates the string value of that element node. The string value of an element node is the concatenation of all its descendant text nodes.
The result of normalize-space(//div[#id='scan-prompt']) is, given the input you show (whitespace marked with "+"):
Scan+A-2+to+prep
Without invoking normalize-space(), for example string(//div[#id='scan-prompt']):
+
Scan+
A-2+
to+prep+
+
So, simply use path expressions that do nothing else than either giving back a string value or a normalized string value. With Google Chrome by using an XPath expression inside $x().

Parsing on HTML some specific datas

I'm working on a small app that requires me to parse an html site on the web.
My problem is as follows :
The parsing routine is working fine for some infos BUT I'm searching for hours for a way to get some infos that refuse to appear.
Here is the partial code structure I'm willing to parse :
<body>
`<header>
<nav>
<div.....>
<aside......>
<main>
<div .....>
<a ......>
<a ......>
</div>
.
.
.
<div id="general">
<h2> ........</h2>
<p>
<span class="label">text</span>
"text 2 to be parsed"
<br>
<span class="label">other text</span>
"text 3 to be parsed"
<br>
just an exemple of structure, to be precise the url is http://www.ourairports.com/airports/EBBR/pilot-info.html
OK it seems that the html code is not appearing on the preview so in the source code of the page above, when you see [div id="general"], below you have a [p] followed by [span class="label"]some text[/span] and just below that you have text between brackets. This happens on several lines and I need to catch those infos .
I've tried with : //body/div/main/div[#id='general']/p as XpathQueryString but result is 1 node and empty
also with div[#id='general'] but result is no node found,
with div[#id='general']/p/span result is no node found,
with //div/p/span[#class='label'] results are the titles between the flags and >/span> but I'm looking to retrieve the text between quotes just behind and I cannot figure out how to succeed. I think I've tried all combinations (a lot others than explained above) but no chance. Is there a special path to get to this text ?
Thanks for your advices.
By the way, this is my very first post on stackoverflow.com and My first language is french, so I do apologize in advance for any rule not followed or my bad english.
Enjoy your day, evening, ... night on the keyboard.
Alain
Your first expression //body/div/main/div[#id='general']/p is expected to return a single node, the <p>. And it works exactly that way on the referred website as you observed. The expression reaches down to that node but not deeper where the text nests. However you must get the text too, just encapsulated in html, with fancy tags around it. A good XPath selector API used properly should return the html node that was matched, including the <p> tag itself.
If all you see in the end is just the text nodes try the following:
Think of the text among the <span>s as html nodes, text() nodes.
//div[#id='general']/p/text()
This will match the "text to be parsed".
A node() will match any html node (even text among tags) and a * any non-text() node.
For any number of steps, use the double slash:
//div[#id='general']/p//text()
Now you match every text node under the <p> tag, regardless of the nesting level. And since text nodes are by definition leaf nodes (cannot contain other nodes), this guarantees that you will not match members of the same path down the tree more than once.
Some comments on you expressions:
//body is superficial, there is only one body and html defines exactly where.
Nodes quantified by #id should not need be proceeded by selectors for their parents, start with //div[#id='something unique'] .
Learn more about XPath. An API that properly returns selected "nodes" and not just concatenated text can play an important role in the understanding of how the expressions work in practice.