diff --git a/plexapi/client.py b/plexapi/client.py index 3d89e3dc6..5436a2a97 100644 --- a/plexapi/client.py +++ b/plexapi/client.py @@ -197,8 +197,7 @@ def query(self, path, method=None, headers=None, timeout=None, **kwargs): raise NotFound(message) else: raise BadRequest(message) - data = utils.cleanXMLString(response.text).encode('utf8') - return ElementTree.fromstring(data) if data.strip() else None + return utils.parseXMLString(response.text) def sendCommand(self, command, proxy=None, **params): """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query` to more easily diff --git a/plexapi/media.py b/plexapi/media.py index 9c6e3115b..0f7f1d7ad 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -import xml from pathlib import Path from urllib.parse import quote_plus +from xml.etree import ElementTree from plexapi import log, settings, utils from plexapi.base import PlexObject @@ -1075,7 +1075,7 @@ def select(self): data = f'{key}?url={quote_plus(self.ratingKey)}' try: self._server.query(data, method=self._server._session.put) - except xml.etree.ElementTree.ParseError: + except ElementTree.ParseError: pass @property diff --git a/plexapi/myplex.py b/plexapi/myplex.py index 448a2649a..c76993539 100644 --- a/plexapi/myplex.py +++ b/plexapi/myplex.py @@ -4,7 +4,6 @@ import threading import time from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit -from xml.etree import ElementTree import requests @@ -250,8 +249,7 @@ def query(self, url, method=None, headers=None, timeout=None, **kwargs): return response.json() elif 'text/plain' in response.headers.get('Content-Type', ''): return response.text.strip() - data = utils.cleanXMLString(response.text).encode('utf8') - return ElementTree.fromstring(data) if data.strip() else None + return utils.parseXMLString(response.text) def ping(self): """ Ping the Plex.tv API. @@ -1879,8 +1877,7 @@ def _query(self, url, method=None, headers=None, **kwargs): codename = codes.get(response.status_code)[0] errtext = response.text.replace('\n', ' ') raise BadRequest(f'({response.status_code}) {codename} {response.url}; {errtext}') - data = response.text.encode('utf8') - return ElementTree.fromstring(data) if data.strip() else None + return utils.parseXMLString(response.text) def _connect(cls, url, token, session, timeout, results, i, job_is_done_event=None): diff --git a/plexapi/server.py b/plexapi/server.py index 8cd110d80..cee77fe66 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -2,7 +2,6 @@ import os from functools import cached_property from urllib.parse import urlencode -from xml.etree import ElementTree import requests @@ -768,8 +767,7 @@ def query(self, key, method=None, headers=None, params=None, timeout=None, **kwa raise NotFound(message) else: raise BadRequest(message) - data = utils.cleanXMLString(response.text).encode('utf8') - return ElementTree.fromstring(data) if data.strip() else None + return utils.parseXMLString(response.text) def search(self, query, mediatype=None, limit=None, sectionId=None): """ Returns a list of media items or filter categories from the resulting diff --git a/plexapi/utils.py b/plexapi/utils.py index dd1cfc9ce..bbff6a8e0 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -17,12 +17,12 @@ from hashlib import sha1 from threading import Event, Thread from urllib.parse import quote +from xml.etree import ElementTree import requests from requests.status_codes import _codes as codes from plexapi.exceptions import BadRequest, NotFound, Unauthorized - try: from tqdm import tqdm except ImportError: @@ -718,3 +718,14 @@ def sha1hash(guid): def cleanXMLString(s): return _illegal_XML_re.sub('', s) + + +def parseXMLString(s: str): + """ Parse an XML string and return an ElementTree object. """ + if not s.strip(): + return None + try: # Attempt to parse the string as-is without cleaning (which is expensive) + return ElementTree.fromstring(s.encode('utf-8')) + except ElementTree.ParseError: # If it fails, clean the string and try again + cleaned_s = cleanXMLString(s).encode('utf-8') + return ElementTree.fromstring(cleaned_s) if cleaned_s.strip() else None