How to check if a text contains an element of an ArrayList - arraylist

I have a text and an array list. I would like to check if my text contains any element in the ArrayList, and if he does find it, to replace the space between two words with "%".
Here's my code :
public static boolean stringContainsItemFromList(String text, ArrayList<String> list)
{
for (String i: list)
{
if (text.contains(list[i]))
{
text.replaceAll(" ", "%");
System.out.println("done");
return true;
}
}
return false;
}
It gives me this error: string cannot be converted to int for the line if (text.contains(list[i])). Any idea how else can I proceed?

i is a String. In that case list[i] does not make sense, since i inside of list[i] should be an index, which is an integer (e.g. list[0], list[1], but can not be list["hello"]).
So if you want to check that your text contains a String from list, you can use
if(text.contains(i))
Also, be aware that your result of
text.replaceAll(" ", "%");
is being ignored. If you want to keep the altered text, you can do that by storing the result of the replace into a new, separate variable.

Your error is because i is a string and you're using it as a int
public static boolean stringContainsItemFromList(String text, ArrayList<String> list)
{
for (String i: list) // HERE: i is an element of the list, not the index!
{
if(text.contains(list[i])) // HERE: list[string] -> error!
{
text.replaceAll(" ", "%");
System.out.println("done");
return true;
}
}
return false;
}
replace text.contains(list[i]) for text.contains(i) and try to use more descriptive var names :)

Something makes me believe, you actually need this !
public static boolean stringContainsItemFromList(String text, ArrayList<String> list)
{
boolean flag = false;
for (String i: list) {
if(text.contains(i)) {
text = text.replaceAll(i, "%");
flag = true;
}
}
System.out.println(text);
System.out.println("done");
return flag;
}

ArrayList<String> list is not an array, it is a list. So you can get the elements of list by calling get() method.
e.g.
list.get(0) // returns the first element of the list
Also
for (String i: list) {
// ...
}
is a foreach loop, a different version of
for(int x=0; x<list.size(); x++) {
// ...
}
i corresponds to list.get(x)
You should change text.contains(list[i]) to text.contains(i) because i stands for list.get(x)
Q: How to check if a text contains an element of an ArrayList
After fixing the error you'll see that the variable text has not changed. Because in Java strings are immutable and replaceAll() method returns a new string. So you should assign the new value to the text variable:
text = text.replaceAll("\\s", "%");
Here is the corrected version of the stringContainsItemFromList() method:
public static boolean stringContainsItemFromList(String text, List<String> list)
{
for (String i: list) {
if(text.contains(i)) {
text = text.replaceAll("\\s", "%");
System.out.println("done");
System.out.println(text);
return true;
}
}
return false;
}

Related

dynamic Using in FindsBy with selenium

I have this spec
Scenario Outline: Display widget
Given I have a valid connection
When I navigate to home using <browser>
Then The element in css selector #<id> > svg > g.x.axis.percent > text:nth-child(1) should be <value>
Examples:
| browser | id | valye |
| Chrome | Widget1 | 213.00 |
With this page definition
class BarSummaryPage
{
[FindsBy(How = How.CssSelector, Using="#{DYNAMIC-ID} > svg > g.x.axis.percent > text:nth-child(1)")]
private IWebElement Mes;
}
I need to configure the Using property in FindsBy dynamic, like above: SEE #{DYNAMIC-ID}
As far as I know, this doesn't exist out of the box. The FindBy annotation takes static Strings only. You probably need to custom modify the FindBy annotation processor similarly to what this blogger did: https://web.archive.org/web/20180612042724/http://brimllc.com/2011/01/selenium-2-0-webdriver-extending-findby-annotation-to-support-dynamic-idxpath/
Another discussion thread here: https://groups.google.com/forum/#!topic/webdriver/awxOw0FoiYU where Simon Stewart shows an example of how this could be accomplished.
UPDATE:
I have actually implemented this because I needed it enough to try. I didn't create a custom finder annotation (which I may have to do in the future).
I wrote implementations for ElementLocator and ElementLocatorFactory that allow for string substitutions for locators specified using the existing annotations. If you know, or can determine, at runtime the values to substitute, this will work for you.
By default, PageFactory uses the classes DefaultElementLocator and DefaultElementLocatorFactory implementations of the ElementLocator and ElementLocatorFactory interfaces for setting up the processing of annotations, but the real logic is in the Annotations class. I wrote my own implementations of ElementLocator, and ElementLocatorFactory and wrote my own version of Annotations to do the processing. There are just a few differences between the source of my customized classes and the ones that are in the Selenium source code.
public class DynamicElementLocator implements ElementLocator {
private static final XLogger log = XLoggerFactory.getXLogger(DynamicElementLocator.class.getCanonicalName());
private final SearchContext searchContext;
private final boolean shouldCache;
private final By by;
private WebElement cachedElement;
private List<WebElement> cachedElementList;
//The only thing that differs from DefaultElementLocator is
//the substitutions parameter for this method.
public DynamicElementLocator(final SearchContext searchContext, final Field field, final Map<String,String>
substitutions) {
log.entry(searchContext, field, substitutions);
this.searchContext = searchContext;
//DynamicAnnotations is my implementation of annotation processing
//that uses the substitutions to find and replace values in the
//locator strings in the FindBy, FindAll, FindBys annotations
DynamicAnnotations annotations = new DynamicAnnotations(field, substitutions);
shouldCache = annotations.isLookupCached();
by = annotations.buildBy();
log.debug("Successful completion of the dynamic element locator");
log.exit();
}
/**
* Find the element.
*/
public WebElement findElement() {
log.entry();
if (cachedElement != null && shouldCache) {
return log.exit(cachedElement);
}
WebElement element = searchContext.findElement(by);
if (shouldCache) {
cachedElement = element;
}
return log.exit(element);
}
/**
* Find the element list.
*/
public List<WebElement> findElements() {
log.entry();
if (cachedElementList != null && shouldCache) {
return log.exit(cachedElementList);
}
List<WebElement> elements = searchContext.findElements(by);
if (shouldCache) {
cachedElementList = elements;
}
return log.exit(elements);
}
}
And here is the DynamicElementLocatorFactory:
public final class DynamicElementLocatorFactory implements ElementLocatorFactory {
private final SearchContext searchContext;
private final Map<String,String> substitutions;
//The only thing that is different from DefaultElementLocatorFactory
//is that the constructor for this class takes the substitutions
//parameter that consists of the key/value mappings to use
//for substituting keys in locator strings for FindBy, FindAll and
//FindBys with values known or determined at runtime.
public DynamicElementLocatorFactory(final SearchContext searchContext, final Map<String,String> substitutions) {
this.searchContext = searchContext;
this.substitutions = substitutions;
}
//This produces an instance of the DynamicElementLocator class and
//specifies the key value mappings to substitute in locator Strings
public DynamicElementLocator createLocator(final Field field) {
return new DynamicElementLocator(searchContext, field, substitutions);
}
}
And here is my custom annotation processor. This is where most of the work was:
public class DynamicAnnotations extends Annotations {
private static final XLogger log = XLoggerFactory.getXLogger(DynamicAnnotations.class.getCanonicalName());
private final Field field;
private final Map<String,String> substitutions;
//Again, not much is different from the Selenium default class here
//other than the additional substitutions parameter
public DynamicAnnotations(final Field field, final Map<String,String> substitutions) {
super(field);
log.entry(field, substitutions);
this.field = field;
this.substitutions = substitutions;
log.debug("Successful completion of the dynamic annotations constructor");
log.exit();
}
public boolean isLookupCached() {
log.entry();
return log.exit((field.getAnnotation(CacheLookup.class) != null));
}
public By buildBy() {
log.entry();
assertValidAnnotations();
By ans = null;
FindBys findBys = field.getAnnotation(FindBys.class);
if (findBys != null) {
log.debug("Building a chained locator");
ans = buildByFromFindBys(findBys);
}
FindAll findAll = field.getAnnotation(FindAll.class);
if (ans == null && findAll != null) {
log.debug("Building a find by one of locator");
ans = buildBysFromFindByOneOf(findAll);
}
FindBy findBy = field.getAnnotation(FindBy.class);
if (ans == null && findBy != null) {
log.debug("Building an ordinary locator");
ans = buildByFromFindBy(findBy);
}
if (ans == null) {
log.debug("No locator annotation specified, so building a locator for id or name based on field name");
ans = buildByFromDefault();
}
if (ans == null) {
throw log.throwing(new IllegalArgumentException("Cannot determine how to locate element " + field));
}
return log.exit(ans);
}
protected By buildByFromDefault() {
log.entry();
return log.exit(new ByIdOrName(field.getName()));
}
protected By buildByFromFindBys(final FindBys findBys) {
log.entry(findBys);
assertValidFindBys(findBys);
FindBy[] findByArray = findBys.value();
By[] byArray = new By[findByArray.length];
for (int i = 0; i < findByArray.length; i++) {
byArray[i] = buildByFromFindBy(findByArray[i]);
}
return log.exit(new ByChained(byArray));
}
protected By buildBysFromFindByOneOf(final FindAll findBys) {
log.entry(findBys);
assertValidFindAll(findBys);
FindBy[] findByArray = findBys.value();
By[] byArray = new By[findByArray.length];
for (int i = 0; i < findByArray.length; i++) {
byArray[i] = buildByFromFindBy(findByArray[i]);
}
return log.exit(new ByAll(byArray));
}
protected By buildByFromFindBy(final FindBy findBy) {
log.entry(findBy);
assertValidFindBy(findBy);
By ans = buildByFromShortFindBy(findBy);
if (ans == null) {
ans = buildByFromLongFindBy(findBy);
}
return log.exit(ans);
}
//The only thing that is different from the default Selenium implementation is that the locator string is processed for substitutions by the processForSubstitutions(using) method, which I have added
protected By buildByFromLongFindBy(final FindBy findBy) {
log.entry(findBy);
How how = findBy.how();
String using = findBy.using();
switch (how) {
case CLASS_NAME:
log.debug("Long FindBy annotation specified lookup by class name, using {}", using);
String className = processForSubstitutions(using);
return log.exit(By.className(className));
case CSS:
log.debug("Long FindBy annotation specified lookup by css name, using {}", using);
String css = processForSubstitutions(using);
return log.exit(By.cssSelector(css));
case ID:
log.debug("Long FindBy annotation specified lookup by id, using {}", using);
String id = processForSubstitutions(using);
return log.exit(By.id(id));
case ID_OR_NAME:
log.debug("Long FindBy annotation specified lookup by id or name, using {}", using);
String idOrName = processForSubstitutions(using);
return log.exit(new ByIdOrName(idOrName));
case LINK_TEXT:
log.debug("Long FindBy annotation specified lookup by link text, using {}", using);
String linkText = processForSubstitutions(using);
return log.exit(By.linkText(linkText));
case NAME:
log.debug("Long FindBy annotation specified lookup by name, using {}", using);
String name = processForSubstitutions(using);
return log.exit(By.name(name));
case PARTIAL_LINK_TEXT:
log.debug("Long FindBy annotation specified lookup by partial link text, using {}", using);
String partialLinkText = processForSubstitutions(using);
return log.exit(By.partialLinkText(partialLinkText));
case TAG_NAME:
log.debug("Long FindBy annotation specified lookup by tag name, using {}", using);
String tagName = processForSubstitutions(using);
return log.exit(By.tagName(tagName));
case XPATH:
log.debug("Long FindBy annotation specified lookup by xpath, using {}", using);
String xpath = processForSubstitutions(using);
return log.exit(By.xpath(xpath));
default:
// Note that this shouldn't happen (eg, the above matches all
// possible values for the How enum)
throw log.throwing(new IllegalArgumentException("Cannot determine how to locate element " + field));
}
}
//The only thing that differs from the default Selenium implementation is that the locator string is processed for substitutions by processForSubstitutions(using), which I wrote
protected By buildByFromShortFindBy(final FindBy findBy) {
log.entry(findBy);
log.debug("Building from a short FindBy annotation");
if (!"".equals(findBy.className())) {
log.debug("Short FindBy annotation specifies lookup by class name: {}", findBy.className());
String className = processForSubstitutions(findBy.className());
return log.exit(By.className(className));
}
if (!"".equals(findBy.css())) {
log.debug("Short FindBy annotation specifies lookup by css");
String css = processForSubstitutions(findBy.css());
return log.exit(By.cssSelector(css));
}
if (!"".equals(findBy.id())) {
log.debug("Short FindBy annotation specified lookup by id");
String id = processForSubstitutions(findBy.id());
return log.exit(By.id(id));
}
if (!"".equals(findBy.linkText())) {
log.debug("Short FindBy annotation specified lookup by link text");
String linkText = processForSubstitutions(findBy.linkText());
return log.exit(By.linkText(linkText));
}
if (!"".equals(findBy.name())) {
log.debug("Short FindBy annotation specified lookup by name");
String name = processForSubstitutions(findBy.name());
return log.exit(By.name(name));
}
if (!"".equals(findBy.partialLinkText())) {
log.debug("Short FindBy annotation specified lookup by partial link text");
String partialLinkText = processForSubstitutions(findBy.partialLinkText());
return log.exit(By.partialLinkText(partialLinkText));
}
if (!"".equals(findBy.tagName())) {
log.debug("Short FindBy annotation specified lookup by tag name");
String tagName = processForSubstitutions(findBy.tagName());
return log.exit(By.tagName(tagName));
}
if (!"".equals(findBy.xpath())) {
log.debug("Short FindBy annotation specified lookup by xpath");
String xpath = processForSubstitutions(findBy.xpath());
return log.exit(By.xpath(xpath));
}
// Fall through
log.debug("Locator does not match any expected locator type");
return log.exit(null);
}
//This method is where I find and do replacements. The method looks
//for instances of ${key} and if there is a key in the substitutions
//map that is equal to 'key', the substring ${key} is replaced by the
//value mapped to 'key'
private String processForSubstitutions(final String locator) {
log.entry(locator);
log.debug("Processing locator '{}' for substitutions");
List<String> subs = Arrays.asList(StringUtils.substringsBetween(locator, "${", "}"));
log.debug("List of substrings in locator which match substitution pattern: {}", subs);
String processed = locator;
for(String sub : subs) {
log.debug("Processing substring {}", sub);
//If there is no matching key, the substring "${ ..}" is treated as a literal
if(substitutions.get(sub) != null) {
log.debug("Replacing with {}", substitutions.get(sub));
processed = StringUtils.replace(locator, "${" + sub + "}",substitutions.get(sub));
log.debug("Locator after substitution: {}", processed);
}
}
return log.exit(processed);
}
private void assertValidAnnotations() {
log.entry();
FindBys findBys = field.getAnnotation(FindBys.class);
FindAll findAll = field.getAnnotation(FindAll.class);
FindBy findBy = field.getAnnotation(FindBy.class);
if (findBys != null && findBy != null) {
throw log.throwing(new IllegalArgumentException("If you use a '#FindBys' annotation, " +
"you must not also use a '#FindBy' annotation"));
}
if (findAll != null && findBy != null) {
throw log.throwing(new IllegalArgumentException("If you use a '#FindAll' annotation, " +
"you must not also use a '#FindBy' annotation"));
}
if (findAll != null && findBys != null) {
throw log.throwing(new IllegalArgumentException("If you use a '#FindAll' annotation, " +
"you must not also use a '#FindBys' annotation"));
}
}
private void assertValidFindBys(final FindBys findBys) {
log.entry(findBys);
for (FindBy findBy : findBys.value()) {
assertValidFindBy(findBy);
}
log.exit();
}
private void assertValidFindAll(final FindAll findBys) {
log.entry(findBys);
for (FindBy findBy : findBys.value()) {
assertValidFindBy(findBy);
}
log.exit();
}
private void assertValidFindBy(final FindBy findBy) {
log.entry();
if (findBy.how() != null) {
if (findBy.using() == null) {
throw log.throwing(new IllegalArgumentException(
"If you set the 'how' property, you must also set 'using'"));
}
}
Set<String> finders = new HashSet<>();
if (!"".equals(findBy.using())) {
log.debug("Locator string is: {}", findBy.using());
finders.add("how: " + findBy.using());
}
if (!"".equals(findBy.className())) {
log.debug("Class name locator string is {}", findBy.className());
finders.add("class name:" + findBy.className());
}
if (!"".equals(findBy.css())) {
log.debug("Css locator string is {}", findBy.css());
finders.add("css:" + findBy.css());
}
if (!"".equals(findBy.id())) {
log.debug("Id locator string is {}", findBy.id());
finders.add("id: " + findBy.id());
}
if (!"".equals(findBy.linkText())) {
log.debug("Link text locator string is {}", findBy.linkText());
finders.add("link text: " + findBy.linkText());
}
if (!"".equals(findBy.name())) {
log.debug("Name locator string is {}", findBy.name());
finders.add("name: " + findBy.name());
}
if (!"".equals(findBy.partialLinkText())) {
log.debug("Partial text locator string is {}", findBy.partialLinkText());
finders.add("partial link text: " + findBy.partialLinkText());
}
if (!"".equals(findBy.tagName())) {
log.debug("Tag name locator string is {}", findBy.tagName());
finders.add("tag name: " + findBy.tagName());
}
if (!"".equals(findBy.xpath())) {
log.debug("Xpath locator string is {}", findBy.xpath());
finders.add("xpath: " + findBy.xpath());
}
// A zero count is okay: it means to look by name or id.
if (finders.size() > 1) {
throw log.throwing(new IllegalArgumentException(
String.format("You must specify at most one location strategy. Number found: %d (%s)",
finders.size(), finders.toString())));
}
}
}
Example usage:
public class ExampleClass extends SlowLoadableComponent<ExampleClass> {
private final Map<String, String> substitutions;
#FindBy(how = How.ID, using = "somelocator_with_a dynamic_${id}")
private WebElement someElement;
public ExampleClass(final WebDriver driver, final int
loadTimeoutInSeconds, final String idValue) {
substitutions = new HashMap<>(); substitutions.put("id", idValue);
}
//When you call PageFactory.initElements, you need to tell it to use the DynamicElementLocatorFactory
protected void load() {
PageFactory.initElements(new DynamicElementLocatorFactory(getDriver(), substitutions), this);
}
}
UPDATED 5/1/2019: I had to use a Web Archive link for the blog post I referenced at the beginning of my answer because that blog post is not accessible at its original link.

Text extraction from table cells

I have a pdf. The pdf contains a table. The table contains many cells (>100). I know the exact position (x,y) and dimension (w,h) of every cell of the table.
I need to extract text from cells using itextsharp. Using PdfReaderContentParser + FilteredTextRenderListener (using a code like this http://itextpdf.com/examples/iia.php?id=279 ) I can extract text but I need to run the whole procedure for each cell. My pdf have many cells and the program needs too much time to run. Is there a way to extract text from a list of "rectangle"? I need to know the text of each rectangle. I'm looking for something like PDFTextStripperByArea by PdfBox (you can define as many regions as you need and the get text using .getTextForRegion("region-name") ).
This option is not immediately included in the iTextSharp distribution but it is easy to realize. In the following I use the iText (Java) class, interface, and method names because I am more at home with Java. They should easily be translatable into iTextSharp (C#) names.
If you use the LocationTextExtractionStrategy, you can can use its a posteriori TextChunkFilter mechanism instead of the a priori FilteredRenderListener mechanism used in the sample you linked to. This mechanism has been introduced in version 5.3.3.
For this you first parse the whole page content using the LocationTextExtractionStrategy without any FilteredRenderListener filtering applied. This makes the strategy object collect TextChunk objects for all PDF text objects on the page containing the associated base line segment.
Then you call the strategy's getResultantText overload with a TextChunkFilter argument (instead of the regular no-argument overload):
public String getResultantText(TextChunkFilter chunkFilter)
You call it with a different TextChunkFilter instance for each table cell. You have to implement this filter interface which is not too difficult as it only defines one method:
public static interface TextChunkFilter
{
/**
* #param textChunk the chunk to check
* #return true if the chunk should be allowed
*/
public boolean accept(TextChunk textChunk);
}
So the accept method of the filter for a given cell must test whether the text chunk in question is inside your cell.
(Instead of separate instances for each cell you can of course also create one instance whose parameters, i.e. cell coordinates, can be changed between getResultantText calls.)
PS: As mentioned by the OP, this TextChunkFilter has not yet been ported to iTextSharp. It should not be hard to do so, though, only one small interface and one method to add to the strategy.
PPS: In a comment sschuberth asked
Do you then still call PdfTextExtractor.getTextFromPage() when using getResultantText(), or does it somehow replace that call? If so, how to you then specify the page to extract to?
Actually PdfTextExtractor.getTextFromPage() internally already uses the no-argument getResultantText() overload:
public static String getTextFromPage(PdfReader reader, int pageNumber, TextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
return parser.processContent(pageNumber, strategy, additionalContentOperators).getResultantText();
}
To make use of a TextChunkFilter you could simply build a similar convenience method, e.g.
public static String getTextFromPage(PdfReader reader, int pageNumber, LocationTextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators, TextChunkFilter chunkFilter) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
return parser.processContent(pageNumber, strategy, additionalContentOperators).getResultantText(chunkFilter);
}
In the context at hand, though, in which we want to parse the page content only once and apply multiple filters, one for each cell, we might generalize this to:
public static List<String> getTextFromPage(PdfReader reader, int pageNumber, LocationTextExtractionStrategy strategy, Map<String, ContentOperator> additionalContentOperators, Iterable<TextChunkFilter> chunkFilters) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
parser.processContent(pageNumber, strategy, additionalContentOperators)
List<String> result = new ArrayList<>();
for (TextChunkFilter chunkFilter : chunkFilters)
{
result.add(strategy).getResultantText(chunkFilter);
}
return result;
}
(You can make this look fancier by using Java 8 collection streaming instead of the old'fashioned for loop.)
Here's my take on how to extract text from a table-like structure in a PDF using itextsharp. It returns a collection of rows and each row contains a collection of interpreted columns. This may work for you on the premise that there is a gap between one column and the next which is greater than the average width of a single character. I also added an option to check for wrapped text within a virtual column. Your mileage may vary.
using (PdfReader pdfReader = new PdfReader(stream))
{
for (int page = 1; page <= pdfReader.NumberOfPages; page++)
{
TableExtractionStrategy tableExtractionStrategy = new TableExtractionStrategy();
string pageText = PdfTextExtractor.GetTextFromPage(pdfReader, page, tableExtractionStrategy);
var table = tableExtractionStrategy.GetTable();
}
}
public class TableExtractionStrategy : LocationTextExtractionStrategy
{
public float NextCharacterThreshold { get; set; } = 1;
public int NextLineLookAheadDepth { get; set; } = 500;
public bool AccomodateWordWrapping { get; set; } = true;
private List<TableTextChunk> Chunks { get; set; } = new List<TableTextChunk>();
public override void RenderText(TextRenderInfo renderInfo)
{
base.RenderText(renderInfo);
string text = renderInfo.GetText();
Vector bottomLeft = renderInfo.GetDescentLine().GetStartPoint();
Vector topRight = renderInfo.GetAscentLine().GetEndPoint();
Rectangle rectangle = new Rectangle(bottomLeft[Vector.I1], bottomLeft[Vector.I2], topRight[Vector.I1], topRight[Vector.I2]);
Chunks.Add(new TableTextChunk(rectangle, text));
}
public List<List<string>> GetTable()
{
List<List<string>> lines = new List<List<string>>();
List<string> currentLine = new List<string>();
float? previousBottom = null;
float? previousRight = null;
StringBuilder currentString = new StringBuilder();
// iterate through all chunks and evaluate
for (int i = 0; i < Chunks.Count; i++)
{
TableTextChunk chunk = Chunks[i];
// determine if we are processing the same row based on defined space between subsequent chunks
if (previousBottom.HasValue && previousBottom == chunk.Rectangle.Bottom)
{
if (chunk.Rectangle.Left - previousRight > 1)
{
currentLine.Add(currentString.ToString());
currentString.Clear();
}
currentString.Append(chunk.Text);
previousRight = chunk.Rectangle.Right;
}
else
{
// if we are processing a new line let's check to see if this could be word wrapping behavior
bool isNewLine = true;
if (AccomodateWordWrapping)
{
int readAheadDepth = Math.Min(i + NextLineLookAheadDepth, Chunks.Count);
if (previousBottom.HasValue)
for (int j = i; j < readAheadDepth; j++)
{
if (previousBottom == Chunks[j].Rectangle.Bottom)
{
isNewLine = false;
break;
}
}
}
// if the text was not word wrapped let's treat this as a new table row
if (isNewLine)
{
if (currentString.Length > 0)
currentLine.Add(currentString.ToString());
currentString.Clear();
previousBottom = chunk.Rectangle.Bottom;
previousRight = chunk.Rectangle.Right;
currentString.Append(chunk.Text);
if (currentLine.Count > 0)
lines.Add(currentLine);
currentLine = new List<string>();
}
else
{
if (chunk.Rectangle.Left - previousRight > 1)
{
currentLine.Add(currentString.ToString());
currentString.Clear();
}
currentString.Append(chunk.Text);
previousRight = chunk.Rectangle.Right;
}
}
}
return lines;
}
private struct TableTextChunk
{
public Rectangle Rectangle;
public string Text;
public TableTextChunk(Rectangle rect, string text)
{
Rectangle = rect;
Text = text;
}
public override string ToString()
{
return Text + " (" + Rectangle.Left + ", " + Rectangle.Bottom + ")";
}
}
}

Recommended way to restrict input in JavaFX textfield

It may seem the question is the duplicate of this. But my question is i have developed a integer textfield in JavaFX by two ways. The code is given below
public class FXNDigitsField extends TextField
{
private long m_digit;
public FXNDigitsField()
{
super();
}
public FXNDigitsField(long number)
{
super();
this.m_digit = number;
onInitialization();
}
private void onInitialization()
{
setText(Long.toString(this.m_digit));
}
#Override
public void replaceText(int startIndex, int endIndex, String text)
{
if (text.matches(Constants.DIGITS_PATTERN) || text.equals(Constants.EMPTY_STRING)) {
super.replaceText(startIndex, endIndex, text);
}
}
#Override
public void replaceSelection(String text)
{
if (text.matches(Constants.DIGITS_PATTERN) || text.equals(Constants.EMPTY_STRING)) {
super.replaceSelection(text);
}
}
}
And the second way is by adding an event Filter.
The code snippet is given.
// restrict key input to numerals.
this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent keyEvent) {
if (!"0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
});
My question is which is the slandered way to do this? Can anyone help me to pick up the right?
The best way to add validation in TextField is a 3rd way.
This method lets TextField finish all processing (copy/paste/undo safe).
It does not require you to extend the TextField class.
And it allows you to decide what to do with new text after every change
(to push it to logic, or turn back to previous value, or even to modify it).
// fired by every text property changes
textField.textProperty().addListener(
(observable, oldValue, newValue) -> {
// Your validation rules, anything you like
// (! note 1 !) make sure that empty string (newValue.equals(""))
// or initial text is always valid
// to prevent inifinity cycle
// do whatever you want with newValue
// If newValue is not valid for your rules
((StringProperty)observable).setValue(oldValue);
// (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
// to anything in your code. TextProperty implementation
// of StringProperty in TextFieldControl
// will throw RuntimeException in this case on setValue(string) call.
// Or catch and handle this exception.
// If you want to change something in text
// When it is valid for you with some changes that can be automated.
// For example change it to upper case
((StringProperty)observable).setValue(newValue.toUpperCase());
}
);
JavaFX has a class TextFormatter for this use-case.
It allows you to validate and adjust the text content before it is "commited" to the textProperty of the TextField.
See this example:
TextFormatter<String> textFormatter = new TextFormatter<>(change -> {
if (!change.isContentChange()) {
return change;
}
String text = change.getControlNewText();
if (isValid(text)) { // your validation logic
return null;
}
return change;
});
textField.setTextFormatter(textFormatter);
In both of your ways, you are not allowed to type characters other then numeric characters. But it will allow to paste any character there (Copy Text from any source and Paste in your TextField).
A good way to do validation is after submitting it,
Like (For integers):
try {
Integer.parseInt(myNumField.getText());
} catch(Exception e) {
System.out.println("Non-numeric character exist");
}
(or you can use any combination of yours + the above method)
Similar of what Manuel Mauky posted, but in groovy is:
Note: This will prevent any other character except for digits to be input.
def digitsOnlyOperator = new UnaryOperator<TextFormatter.Change>() {
#Override
TextFormatter.Change apply(TextFormatter.Change change) {
return !change.contentChange || change.controlNewText.isNumber() ? change : null
}
}
textField.textFormatter = new TextFormatter<>(digitsOnlyOperator)
There is probably a shorter way to do it in groovy. If you know it, please post it here.
This code snippet allows just digits to be entered by the user in a TextField.
/**
* This will check whether the incoming data from the user is valid.
*/
UnaryOperator<TextFormatter.Change> numberValidationFormatter = change -> {
if (change.getText().matches("\\d+")) {
return change; //if change is a number
} else {
change.setText(""); //else make no change
change.setRange( //don't remove any selected text either.
change.getRangeStart(),
change.getRangeStart()
);
return change;
}
};
TextFormatter tf = new TextFormatter(numberValidationFormatter);
textfield.setTextFormatter(tf);

What is the recommended way to make a numeric TextField in JavaFX?

I need to restrict input into a TextField to integers. Any advice?
Very old thread, but this seems neater and strips out non-numeric characters if pasted.
// force the field to be numeric only
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (!newValue.matches("\\d*")) {
textField.setText(newValue.replaceAll("[^\\d]", ""));
}
}
});
javafx.scene.control.TextFormatter
Updated Apr 2016
This answer was created some years ago and the original answer is largely obsolete now.
Since Java 8u40, Java has a TextFormatter which is usually best for enforcing input of specific formats such as numerics on JavaFX TextFields:
Numeric TextField for Integers in JavaFX 8 with TextFormatter and/or UnaryOperator
Java 8 U40 TextFormatter (JavaFX) to restrict user input only for decimal number
String with numbers and letters to double javafx
See also other answers to this question which specifically mention TextFormatter.
Original Answer
There are some examples of this in this gist, I have duplicated one of the examples below:
// helper text field subclass which restricts text input to a given range of natural int numbers
// and exposes the current numeric int value of the edit box as a value property.
class IntField extends TextField {
final private IntegerProperty value;
final private int minValue;
final private int maxValue;
// expose an integer value property for the text field.
public int getValue() { return value.getValue(); }
public void setValue(int newValue) { value.setValue(newValue); }
public IntegerProperty valueProperty() { return value; }
IntField(int minValue, int maxValue, int initialValue) {
if (minValue > maxValue)
throw new IllegalArgumentException(
"IntField min value " + minValue + " greater than max value " + maxValue
);
if (!((minValue <= initialValue) && (initialValue <= maxValue)))
throw new IllegalArgumentException(
"IntField initialValue " + initialValue + " not between " + minValue + " and " + maxValue
);
// initialize the field values.
this.minValue = minValue;
this.maxValue = maxValue;
value = new SimpleIntegerProperty(initialValue);
setText(initialValue + "");
final IntField intField = this;
// make sure the value property is clamped to the required range
// and update the field's text to be in sync with the value.
value.addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
if (newValue == null) {
intField.setText("");
} else {
if (newValue.intValue() < intField.minValue) {
value.setValue(intField.minValue);
return;
}
if (newValue.intValue() > intField.maxValue) {
value.setValue(intField.maxValue);
return;
}
if (newValue.intValue() == 0 && (textProperty().get() == null || "".equals(textProperty().get()))) {
// no action required, text property is already blank, we don't need to set it to 0.
} else {
intField.setText(newValue.toString());
}
}
}
});
// restrict key input to numerals.
this.addEventFilter(KeyEvent.KEY_TYPED, new EventHandler<KeyEvent>() {
#Override public void handle(KeyEvent keyEvent) {
if(intField.minValue<0) {
if (!"-0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
else {
if (!"0123456789".contains(keyEvent.getCharacter())) {
keyEvent.consume();
}
}
}
});
// ensure any entered values lie inside the required range.
this.textProperty().addListener(new ChangeListener<String>() {
#Override public void changed(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
if (newValue == null || "".equals(newValue) || (intField.minValue<0 && "-".equals(newValue))) {
value.setValue(0);
return;
}
final int intValue = Integer.parseInt(newValue);
if (intField.minValue > intValue || intValue > intField.maxValue) {
textProperty().setValue(oldValue);
}
value.set(Integer.parseInt(textProperty().get()));
}
});
}
}
I know this is a rather old thread, but for future readers here is another solution I found quite intuitive:
public class NumberTextField extends TextField
{
#Override
public void replaceText(int start, int end, String text)
{
if (validate(text))
{
super.replaceText(start, end, text);
}
}
#Override
public void replaceSelection(String text)
{
if (validate(text))
{
super.replaceSelection(text);
}
}
private boolean validate(String text)
{
return text.matches("[0-9]*");
}
}
Edit: Thanks none_ and SCBoy for your suggested improvements.
Starting with JavaFX 8u40, you can set a TextFormatter object on a text field:
UnaryOperator<Change> filter = change -> {
String text = change.getText();
if (text.matches("[0-9]*")) {
return change;
}
return null;
};
TextFormatter<String> textFormatter = new TextFormatter<>(filter);
fieldNport = new TextField();
fieldNport.setTextFormatter(textFormatter);
This avoids both subclassing and duplicate change events which you will get when you add a change listener to the text property and modify the text in that listener.
The TextInput has a TextFormatter which can be used to format, convert and limit the types of text that can be input.
The TextFormatter has a filter which can be used to reject input. We need to set this to reject anything that's not a valid integer. It also has a converter which we need to set to convert the string value to an integer value which we can bind later on.
Lets create a reusable filter:
public class IntegerFilter implements UnaryOperator<TextFormatter.Change> {
private final static Pattern DIGIT_PATTERN = Pattern.compile("\\d*");
#Override
public Change apply(TextFormatter.Change aT) {
return DIGIT_PATTERN.matcher(aT.getText()).matches() ? aT : null;
}
}
The filter can do one of three things, it can return the change unmodified to accept it as it is, it can alter the change in some way it deems fit or it can return null to reject the change all together.
We will use the standard IntegerStringConverter as a converter.
Putting it all together we have:
TextField textField = ...;
TextFormatter<Integer> formatter = new TextFormatter<>(
new IntegerStringConverter(), // Standard converter form JavaFX
defaultValue,
new IntegerFilter());
formatter.valueProperty().bindBidirectional(myIntegerProperty);
textField.setTextFormatter(formatter);
If you want don't need a reusable filter you can do this fancy one-liner instead:
TextFormatter<Integer> formatter = new TextFormatter<>(
new IntegerStringConverter(),
defaultValue,
c -> Pattern.matches("\\d*", c.getText()) ? c : null );
I don't like exceptions thus I used the matches function from String-Class
text.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (newValue.matches("\\d*")) {
int value = Integer.parseInt(newValue);
} else {
text.setText(oldValue);
}
}
});
Starting from Java SE 8u40, for such need you can use an "integer" Spinner allowing to safely select a valid integer by using the keyboard's up arrow/down arrow keys or the up arrow/down arrow provided buttons.
You can also define a min, a max and an initial value to limit the allowed values and an amount to increment or decrement by, per step.
For example
// Creates an integer spinner with 1 as min, 10 as max and 2 as initial value
Spinner<Integer> spinner1 = new Spinner<>(1, 10, 2);
// Creates an integer spinner with 0 as min, 100 as max and 10 as initial
// value and 10 as amount to increment or decrement by, per step
Spinner<Integer> spinner2 = new Spinner<>(0, 100, 10, 10);
Example of result with an "integer" spinner and a "double" spinner
A spinner is a single-line text field control that lets the user
select a number or an object value from an ordered sequence of such
values. Spinners typically provide a pair of tiny arrow buttons for
stepping through the elements of the sequence. The keyboard's up
arrow/down arrow keys also cycle through the elements. The user may
also be allowed to type a (legal) value directly into the spinner.
Although combo boxes provide similar functionality, spinners are
sometimes preferred because they don't require a drop-down list that
can obscure important data, and also because they allow for features
such as wrapping from the maximum value back to the minimum value
(e.g., from the largest positive integer to 0).
More details about the Spinner control
The preffered answer can be even smaller if you make use of Java 1.8 Lambdas
textfield.textProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.matches("\\d*")) return;
textfield.setText(newValue.replaceAll("[^\\d]", ""));
});
I want to help with my idea from combining Evan Knowles answer with TextFormatter from JavaFX 8
textField.setTextFormatter(new TextFormatter<>(c -> {
if (!c.getControlNewText().matches("\\d*"))
return null;
else
return c;
}
));
so good luck ;) keep calm and code java
TextField text = new TextField();
text.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable,
String oldValue, String newValue) {
try {
Integer.parseInt(newValue);
if (newValue.endsWith("f") || newValue.endsWith("d")) {
manualPriceInput.setText(newValue.substring(0, newValue.length()-1));
}
} catch (ParseException e) {
text.setText(oldValue);
}
}
});
The if clause is important to handle inputs like 0.5d or 0.7f which are correctly parsed by Int.parseInt(), but shouldn't appear in the text field.
Try this simple code it will do the job.
DecimalFormat format = new DecimalFormat( "#.0" );
TextField field = new TextField();
field.setTextFormatter( new TextFormatter<>(c ->
{
if ( c.getControlNewText().isEmpty() )
{
return c;
}
ParsePosition parsePosition = new ParsePosition( 0 );
Object object = format.parse( c.getControlNewText(), parsePosition );
if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
{
return null;
}
else
{
return c;
}
}));
If you want to apply the same listener to more than one TextField here is the simplest solution:
TextField txtMinPrice, txtMaxPrice = new TextField();
ChangeListener<String> forceNumberListener = (observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*"))
((StringProperty) observable).set(oldValue);
};
txtMinPrice.textProperty().addListener(forceNumberListener);
txtMaxPrice.textProperty().addListener(forceNumberListener);
This one worked for me.
public void RestrictNumbersOnly(TextField tf){
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue) {
if (!newValue.matches("|[-\\+]?|[-\\+]?\\d+\\.?|[-\\+]?\\d+\\.?\\d+")){
tf.setText(oldValue);
}
}
});
}
Here is a simple class that handles some basic validations on TextField, using TextFormatter introduced in JavaFX 8u40
EDIT:
(Code added regarding Floern's comment)
import java.text.DecimalFormatSymbols;
import java.util.regex.Pattern;
import javafx.beans.NamedArg;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
public class TextFieldValidator {
private static final String CURRENCY_SYMBOL = DecimalFormatSymbols.getInstance().getCurrencySymbol();
private static final char DECIMAL_SEPARATOR = DecimalFormatSymbols.getInstance().getDecimalSeparator();
private final Pattern INPUT_PATTERN;
public TextFieldValidator(#NamedArg("modus") ValidationModus modus, #NamedArg("countOf") int countOf) {
this(modus.createPattern(countOf));
}
public TextFieldValidator(#NamedArg("regex") String regex) {
this(Pattern.compile(regex));
}
public TextFieldValidator(Pattern inputPattern) {
INPUT_PATTERN = inputPattern;
}
public static TextFieldValidator maxFractionDigits(int countOf) {
return new TextFieldValidator(maxFractionPattern(countOf));
}
public static TextFieldValidator maxIntegers(int countOf) {
return new TextFieldValidator(maxIntegerPattern(countOf));
}
public static TextFieldValidator integersOnly() {
return new TextFieldValidator(integersOnlyPattern());
}
public TextFormatter<Object> getFormatter() {
return new TextFormatter<>(this::validateChange);
}
private Change validateChange(Change c) {
if (validate(c.getControlNewText())) {
return c;
}
return null;
}
public boolean validate(String input) {
return INPUT_PATTERN.matcher(input).matches();
}
private static Pattern maxFractionPattern(int countOf) {
return Pattern.compile("\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?");
}
private static Pattern maxCurrencyFractionPattern(int countOf) {
return Pattern.compile("^\\" + CURRENCY_SYMBOL + "?\\s?\\d*(\\" + DECIMAL_SEPARATOR + "\\d{0," + countOf + "})?\\s?\\" +
CURRENCY_SYMBOL + "?");
}
private static Pattern maxIntegerPattern(int countOf) {
return Pattern.compile("\\d{0," + countOf + "}");
}
private static Pattern integersOnlyPattern() {
return Pattern.compile("\\d*");
}
public enum ValidationModus {
MAX_CURRENCY_FRACTION_DIGITS {
#Override
public Pattern createPattern(int countOf) {
return maxCurrencyFractionPattern(countOf);
}
},
MAX_FRACTION_DIGITS {
#Override
public Pattern createPattern(int countOf) {
return maxFractionPattern(countOf);
}
},
MAX_INTEGERS {
#Override
public Pattern createPattern(int countOf) {
return maxIntegerPattern(countOf);
}
},
INTEGERS_ONLY {
#Override
public Pattern createPattern(int countOf) {
return integersOnlyPattern();
}
};
public abstract Pattern createPattern(int countOf);
}
}
You can use it like this:
textField.setTextFormatter(new TextFieldValidator(ValidationModus.INTEGERS_ONLY).getFormatter());
or you can instantiate it in a fxml file, and apply it to a customTextField with the according properties.
app.fxml:
<fx:define>
<TextFieldValidator fx:id="validator" modus="INTEGERS_ONLY"/>
</fx:define>
CustomTextField.class:
public class CustomTextField {
private TextField textField;
public CustomTextField(#NamedArg("validator") TextFieldValidator validator) {
this();
textField.setTextFormatter(validator.getFormatter());
}
}
Code on github
This is what I use:
private TextField textField;
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if(!newValue.matches("[0-9]*")){
textField.setText(oldValue);
}
}
});
The same in lambda notation would be:
private TextField textField;
textField.textProperty().addListener((observable, oldValue, newValue) -> {
if(!newValue.matches("[0-9]*")){
textField.setText(oldValue);
}
});
This method lets TextField to finish all processing (copy/paste/undo safe).
Does not require to extend classes and allows you to decide what to do with new text after every change
(to push it to logic, or turn back to previous value, or even to modify it).
// fired by every text property change
textField.textProperty().addListener(
(observable, oldValue, newValue) -> {
// Your validation rules, anything you like
// (! note 1 !) make sure that empty string (newValue.equals(""))
// or initial text is always valid
// to prevent inifinity cycle
// do whatever you want with newValue
// If newValue is not valid for your rules
((StringProperty)observable).setValue(oldValue);
// (! note 2 !) do not bind textProperty (textProperty().bind(someProperty))
// to anything in your code. TextProperty implementation
// of StringProperty in TextFieldControl
// will throw RuntimeException in this case on setValue(string) call.
// Or catch and handle this exception.
// If you want to change something in text
// When it is valid for you with some changes that can be automated.
// For example change it to upper case
((StringProperty)observable).setValue(newValue.toUpperCase());
}
);
For your case just add this logic inside. Works perfectly.
if (newValue.equals("")) return;
try {
Integer i = Integer.valueOf(newValue);
// do what you want with this i
} catch (Exception e) {
((StringProperty)observable).setValue(oldValue);
}
Mmmm. I ran into that problem weeks ago. As the API doesn't provide a control to achieve that,
you may want to use your own one. I used something like:
public class IntegerBox extends TextBox {
public-init var value : Integer = 0;
protected function apply() {
try {
value = Integer.parseInt(text);
} catch (e : NumberFormatException) {}
text = "{value}";
}
override var focused = false on replace {apply()};
override var action = function () {apply()}
}
It's used the same way that a normal TextBox,
but has also a value attribute which stores the entered integer.
When the control looses the focus, it validates the value and reverts it (if isn't valid).
this Code Make your textField Accept only Number
textField.lengthProperty().addListener((observable, oldValue, newValue) -> {
if(newValue.intValue() > oldValue.intValue()){
char c = textField.getText().charAt(oldValue.intValue());
/** Check if the new character is the number or other's */
if( c > '9' || c < '0'){
/** if it's not number then just setText to previous one */
textField.setText(textField.getText().substring(0,textField.getText().length()-1));
}
}
});
This code works fine for me even if you try to copy/paste.
myTextField.textProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue.matches("\\d*")) {
myTextField.setText(oldValue);
}
});
In recent updates of JavaFX, you have to set new text in Platform.runLater method just like this:
private void set_normal_number(TextField textField, String oldValue, String newValue) {
try {
int p = textField.getCaretPosition();
if (!newValue.matches("\\d*")) {
Platform.runLater(() -> {
textField.setText(newValue.replaceAll("[^\\d]", ""));
textField.positionCaret(p);
});
}
} catch (Exception e) {
}
}
It's a good idea to set caret position too.
I would like to improve Evan Knowles answer: https://stackoverflow.com/a/30796829/2628125
In my case I had class with handlers for UI Component part. Initialization:
this.dataText.textProperty().addListener((observable, oldValue, newValue) -> this.numericSanitization(observable, oldValue, newValue));
And the numbericSanitization method:
private synchronized void numericSanitization(ObservableValue<? extends String> observable, String oldValue, String newValue) {
final String allowedPattern = "\\d*";
if (!newValue.matches(allowedPattern)) {
this.dataText.setText(oldValue);
}
}
Keyword synchronized is added to prevent possible render lock issue in javafx if setText will be called before old one is finished execution. It is easy to reproduce if you will start typing wrong chars really fast.
Another advantage is that you keep only one pattern to match and just do rollback. It is better because you can easily abstragate solution for different sanitization patterns.
rate_text.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
String s="";
for(char c : newValue.toCharArray()){
if(((int)c >= 48 && (int)c <= 57 || (int)c == 46)){
s+=c;
}
}
rate_text.setText(s);
}
});
This works fine as it allows you to enter only integer value and decimal value (having ASCII code 46).
Another very simple solution would be:
TextField tf = new TextField();
tf.addEventFilter(KeyEvent.ANY, event -> {
if (!event.getCharacter().trim().matches("\\d?")) {
event.consume();
}
});
A little late, but if you also what to include decimals:
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (!newValue.matches("\\d{0,7}([\\.]\\d{0,4})?")) {
textField.setText(oldValue);
}
}

How to find the value of an attribute

How can I find the value of an attribute? I need to check the value and set the textbox maxlength to that value. Here is an example of the value I'm looking to retrieve.
public class DogClass
{
[StringLength(5)]
public string LegalName
{
}
You can use reflection to get this info. Below is a snippet that should get you started.
protected void GetStringLength(object objDog) {
// loop through each property in object
foreach (PropertyInfo pi in objDog.GetType().GetProperties())
{
// for each object property, get the SringLength tag (if there is one)
foreach (Attribute attribute in Attribute.GetCustomAttributes(pi, typeof(StringLengthAttribute), true))
{
// we'll assume there is only one
var stringLenVal = (attribute as StringLengthAttribute).MaximumLength;
break;
}
}
}