Как использовать Python XML findall для поиска '‹v: imagedata r: id = rId7 o: title = 1-REN /›'

Я пытаюсь найти все из документа Word для <v:imagedata r:id="rId7" o:title="1-REN"/> с пространством имен xmlns:v="urn:schemas-microsoft-com:vml", и я не могу понять, что это за синтаксис.

документы охватывают только очень простой случай и с Добавлена ​​комбинация URN и VML. Кажется, я не могу заставить работать ни один из примеров, которые я видел в Интернете. Кто-нибудь случайно знает, что это такое?

Я пытаюсь сделать что-то вроде этого:

namespace = {'v': "urn:schemas-microsoft-com:vml"}

results = ET.fromstring(xml).findall("imagedata", namespace)
for image_id in results:
    print(image_id)

Изменить: то, что написал @aneroid, на 1000% правильный ответ и очень полезно. Вы должны проголосовать за него. Тем не менее, после понимания всего этого - я пошел с ответом BS4, потому что он выполняет всю работу в две строки именно так, как мне нужно ????. Если вам на самом деле не нужны пространства имен, это будет проще.


person Grant Curell    schedule 31.05.2020    source источник


Ответы (3)


ET.findall() vs BS4.find_all():

  • ElementTree's findall() is not recursive by default*. It's only going to find direct children of the node provided. So in your case, it's only searching for image nodes directly under the root element.
    • * as per mzjn's comment below, prefixing the match argument (tag or path) with ".//" will search for that node anywhere in the tree, since it's supports XPath's.
  • find_all() BeautifulSoup ищет всех потомков . Таким образом, он ищет узлы «imagedata» в любом месте дерева.
  • Однако _7 _ выполняет поиск всех потомков. Использование примера "работа с пространствами имен" в документации:

    >>> for char in root.iter('{http://characters.example.com}character'):
    ...     print(' |-->', char.text)
    ...
     |--> Lancelot
     |--> Archie Leach
     |--> Sir Robin
     |--> Gunther
     |--> Commander Clement
    
  • Sadly, ET.iterfind() which works with namespaces as a dict (like ET.findall), also does not search descendants, only direct children by default*. Just like ET.findall. Apart from how empty strings '' in the tags are treated wrt the namespace, and one returns a list while the other returns an iterator, I can't say there's a meaningful difference between ET.findall and ET.iterfind.
    • * As above for ET.findall(), prefixing ".//" makes it search the entire tree (matches with any node).

Когда вы используете пространства имен с ET, вам все равно понадобится имя пространства имен с тегом. Строка результатов должна быть:

namespace = {'v': "urn:schemas-microsoft-com:vml"}
results = ET.fromstring(xml).findall("v:imagedata", namespace)  # note the 'v:'

Кроме того, 'v' не обязательно должен быть 'v', при необходимости вы можете изменить его на что-то более значимое:

namespace = {'image': "urn:schemas-microsoft-com:vml"}
results = ET.fromstring(xml).findall("image:imagedata", namespace)

Конечно, это не обязательно даст вам все элементы imagedata, если они не являются прямыми дочерними элементами корня. Для этого вам нужно создать рекурсивную функцию, которая сделает это за вас. См. этот ответ на SO, чтобы узнать, как это сделать. Обратите внимание: хотя этот ответ выполняет рекурсивный поиск, вы, вероятно, достигнете предела рекурсии Python, если глубина потомка будет слишком ... глубокой.

Чтобы получить все элементы imagedata в любом месте дерева, используйте префикс ".//":

results = ET.fromstring(xml).findall(".//v:imagedata", namespace)
person aneroid    schedule 31.05.2020
comment
findall может найти все imagedata узлы. Просто используйте findall(".//v:imagedata", namespace). - person mzjn; 31.05.2020
comment
Спасибо! Я отредактировал и разъяснил свой ответ как по ET.findall(), так и по ET.iterfind(). - person aneroid; 01.06.2020

С ElementTree в Python 3.8 вы можете просто использовать подстановочный знак ({*}) для пространства имен:

results = ET.fromstring(xml).findall(".//{*}imagedata") 

Обратите внимание на часть .//, которая означает, что поиск выполняется по всему документу (все потомки).

person mzjn    schedule 31.05.2020
comment
Это также работает для .iter() (и, вероятно, для всех методов, связанных с поиском). Кроме того, бит .// относится к конкретному вопросу. Новый подстановочный знак {*} использовать не обязательно. - person Aaron; 11.09.2020
comment
Действительно ли подстановочный знак работает с iter()? Связанные примечания к выпуску 3.8 упоминают только .find*() методы. - person mzjn; 11.09.2020
comment
Да, я подтвердил воровское поведение. Документация о «Поддерживаемом синтаксисе XPath» теперь также указывает: {*}spam selects tags named spam in any (or no) namespace и Changed in version 3.8: Support for star-wildcards was added. - person Aaron; 11.09.2020
comment
@Aaron: Что вы имеете в виду, говоря, что это также работает для .iter()? Подстановочный знак пространства имен не работает с iter(). Работает с find(), findall() и findtext(). - person mzjn; 12.09.2020
comment
Из моего местного тестирования; новый подстановочный знак работает в iter(). Из краткого обзора; кажется, что фиксация исходного кода изменяет логику сравнения, используемую для определения совпадения тегов, так что я предполагаю, что можно сказать, что это работает для .iter(), даже если это не задокументировано. Однако это только предположение, пока кто-то не добавит соответствующие модульные тесты и не обновит документацию. - person Aaron; 17.09.2020
comment
Извините ... вы были правы @mzjn. Отправившись защищать свою честь, я обнаружил, что мои самодельные юнит-тесты ElementTree провалились. Оказывается, ваш ответ указал мне правильное направление синтаксиса подстановочного знака, присутствующего в lxml пакет, который я использую в своем проекте. Совершенно другая библиотека синтаксического анализа XML на Python. Я увижу себя. - person Aaron; 18.09.2020

Я собираюсь оставить вопрос открытым, но в настоящее время я использую обходной путь - это использование BeautifulSoup, который с радостью принимает синтаксис v:.

soup = BeautifulSoup(xml, "lxml")

results = soup.find_all("v:imagedata")
person Grant Curell    schedule 31.05.2020