scrape-cli 1.3.1__tar.gz → 1.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrape_cli
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.
5
5
  Author-email: Andrea Borruso <aborruso@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/aborruso/scrape-cli
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scrape_cli"
7
- version = "1.3.1"
7
+ version = "1.3.2"
8
8
  description = "It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -4,7 +4,7 @@ scrape-cli - A command-line tool to extract HTML elements using XPath or CSS3 se
4
4
 
5
5
  from scrape_cli.scrape import main
6
6
 
7
- __version__ = "1.3.1"
7
+ __version__ = "1.3.2"
8
8
  __author__ = "Andrea Borruso"
9
9
  __author_email__ = "aborruso@gmail.com"
10
10
 
@@ -56,6 +56,21 @@ def convert_css_to_xpath(expression):
56
56
  print(f"Error converting CSS selector to XPath: {e}", file=sys.stderr)
57
57
  sys.exit(1)
58
58
 
59
+ def xpath_result_items(result):
60
+ """XPath functions return a single string, number or boolean instead of a node list."""
61
+ if isinstance(result, list):
62
+ return result
63
+ if isinstance(result, bool):
64
+ return ['true' if result else 'false']
65
+ if isinstance(result, float):
66
+ return [str(int(result)) if result.is_integer() else str(result)]
67
+ return [result] if result else []
68
+
69
+
70
+ def wrap_in_body(results):
71
+ return "<!DOCTYPE html>\n<html>\n<body>\n" + "".join(r + "\n" for r in results) + "</body>\n</html>\n"
72
+
73
+
59
74
  def is_xpath(expression):
60
75
  """
61
76
  Check if the expression is XPath by looking for common XPath patterns:
@@ -120,12 +135,10 @@ examples:
120
135
  parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
121
136
 
122
137
  # Check for incorrect argument order (-eb instead of -be, -ej instead of -je)
123
- if '-eb' in sys.argv[1:]:
124
- print("Error: use -be not -eb.\n scrape -be \"//article\" file.html", file=sys.stderr)
125
- sys.exit(1)
126
- if '-ej' in sys.argv[1:]:
127
- print("Error: use -je not -ej.\n scrape -je \"//article\" file.html", file=sys.stderr)
128
- sys.exit(1)
138
+ for wrong, right in (('-eb', '-be'), ('-ej', '-je')):
139
+ if wrong in sys.argv[1:]:
140
+ print(f"Error: use {right} not {wrong}.\n scrape {right} \"//article\" file.html", file=sys.stderr)
141
+ sys.exit(1)
129
142
  # Defines the HTML input argument (can be a file, URL or stdin)
130
143
  parser.add_argument('html', nargs='?', type=str, default='',
131
144
  help="HTML input (file, URL or stdin, default: stdin)", metavar="HTML")
@@ -266,11 +279,13 @@ examples:
266
279
  string_results = []
267
280
  # For each expression, perform the search in the parsed HTML
268
281
  for e in expression:
269
- els = list(document.xpath(e))
282
+ result = document.xpath(e)
270
283
 
271
284
  # If check-existence is enabled, return 0 or 1 depending on the existence of elements
272
285
  if args.check_existence:
273
- sys.exit(1 if len(els) == 0 else 0)
286
+ sys.exit(0 if result else 1)
287
+
288
+ els = xpath_result_items(result)
274
289
 
275
290
  # Extract the text or content of the found elements
276
291
  for el in els:
@@ -314,19 +329,15 @@ examples:
314
329
  # Only strings (@attr, text()): XML conversion would merge them into one
315
330
  parsed = {"html": {"body": values[0] if len(values) == 1 else values}}
316
331
  else:
317
- wrapped = "<!DOCTYPE html>\n<html>\n<body>\n" + "".join(r + "\n" for r in results) + "</body>\n</html>\n"
318
332
  try:
319
- parsed = xmltodict.parse(wrapped)
333
+ parsed = xmltodict.parse(wrap_in_body(results))
320
334
  except ExpatError as e:
321
335
  print(f"Error: cannot convert result to JSON, it is not valid XML ({e}). "
322
336
  "Attribute names like @click or :bind are not supported by -j.", file=sys.stderr)
323
337
  sys.exit(1)
324
338
  sys.stdout.write(json.dumps(parsed, indent=2, ensure_ascii=False) + "\n")
325
339
  elif args.body and not args.text:
326
- sys.stdout.write("<!DOCTYPE html>\n<html>\n<body>\n")
327
- for result in results:
328
- sys.stdout.write(result + "\n")
329
- sys.stdout.write("</body>\n</html>\n")
340
+ sys.stdout.write(wrap_in_body(results))
330
341
  else:
331
342
  # Normal output (or text-only output when -t is used)
332
343
  for result in results:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrape_cli
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.
5
5
  Author-email: Andrea Borruso <aborruso@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/aborruso/scrape-cli
@@ -288,6 +288,43 @@ def test_json_flag_mixed_elements_and_strings_still_converts():
288
288
  assert body["#text"] == "x&y"
289
289
 
290
290
 
291
+ SCALAR_HTML = "<h1>Ciao</h1><a>1</a><a>2</a>"
292
+
293
+
294
+ def test_xpath_functions_return_a_single_value():
295
+ cases = {
296
+ "string(//h1)": "Ciao\n",
297
+ "count(//a)": "2\n",
298
+ "boolean(//a)": "true\n",
299
+ "boolean(//nulla)": "false\n",
300
+ "string(//nulla)": "",
301
+ }
302
+ for expression, expected in cases.items():
303
+ result = run_scrape("-e", expression, input_data=SCALAR_HTML)
304
+ assert result.returncode == 0, result.stderr
305
+ assert result.stdout == expected, expression
306
+
307
+
308
+ def test_check_existence_follows_xpath_function_value():
309
+ for expression, expected in {
310
+ "string(//h1)": 0,
311
+ "string(//nulla)": 1,
312
+ "count(//a)": 0,
313
+ "count(//nulla)": 1,
314
+ "boolean(//a)": 0,
315
+ "boolean(//nulla)": 1,
316
+ }.items():
317
+ result = run_scrape("-x", "-e", expression, input_data=SCALAR_HTML)
318
+ assert result.returncode == expected, expression
319
+
320
+
321
+ def test_json_flag_with_xpath_functions():
322
+ for expression, expected in {"string(//h1)": "Ciao", "count(//a)": "2"}.items():
323
+ result = run_scrape("-je", expression, input_data=SCALAR_HTML)
324
+ assert result.returncode == 0, result.stderr
325
+ assert json.loads(result.stdout)["html"]["body"] == expected
326
+
327
+
291
328
  def test_json_flag_text_results_work_on_test_file():
292
329
  result = run_scrape(str(TEST_HTML), "-je", "//p/text()")
293
330
 
File without changes
File without changes