.NET: Есть ли способ найти пространство имен по умолчанию в запросе XPath 1.0? - PullRequest
3 голосов
/ 01 апреля 2010

Я создаю инструмент, который выполняет запросы xpath 1.0 для документов XHTML. Требование использовать префикс пространства имен в запросе убивает меня. Запрос выглядит так:

html/body/div[@class='contents']/div[@class='body']/
    div[@class='pgdbbyauthor']/h2[a[@name][starts-with(.,'Quick')]]/
    following-sibling::ul[1]/li/a

(все в одной строке)

... что достаточно плохо, за исключением того, что это xpath 1.0, мне нужно использовать явный префикс пространства имен для каждого QName, поэтому это выглядит так:

ns1:html/ns1:body/ns1:div[@class='contents']/ns1:div[@class='body']/
    ns1:div[@class='pgdbbyauthor']/ns1:h2[ns1:a[@name][starts-with(.,'Quick')]]/
    following-sibling::ns1:ul[1]/ns1:li/ns1:a

Чтобы настроить запрос, я делаю что-то вроде этого:

var xpathDoc = new XPathDocument(new StringReader(theText));
var nav = xpathDoc.CreateNavigator();
var xmlns = new XmlNamespaceManager(nav.NameTable);
foreach (string prefix in xmlNamespaces.Keys)
    xmlns.AddNamespace(prefix, xmlNamespaces[prefix]);    
XPathNodeIterator selection = nav.Select(xpathExpression, xmlns);

Но я хочу, чтобы xpathExpression использовал неявное пространство имен по умолчанию.

Есть ли способ для меня преобразовать неукрашенное выражение xpath после того, как оно было написано, чтобы вставить префикс пространства имен для каждого имени элемента в запросе?

Я думаю, что-нибудь между двумя слешами, я мог бы вставить туда префикс. Исключая имена осей курса, такие как «parent ::» и «previous-sibling ::». И подстановочные знаки. Вот что я имею в виду под " finagle пространством имен по умолчанию".

Этот хак сработает?


Добавление
Вот что я имею в виду. Предположим, у меня есть выражение xpath, и, прежде чем передать его в nav.Select (), я преобразую его. Примерно так:

string FixupWithDefaultNamespace(string expr)
{
    string s = expr;
    s = Regex.Replace(s, "^(?!::)([^/:]+)(?=/)", "ns1:$1");                        // beginning
    s = Regex.Replace(s, "/([^/:]+)(?=/)", "/ns1:$1");                             // stanza
    s = Regex.Replace(s, "::([A-Za-z][^/:*]*)(?=/)", "::ns1:$1");                  // axis specifier
    s = Regex.Replace(s, "\\[([A-Za-z][^/:*\\(]*)(?=[\\[\\]])", "[ns1:$1");        // predicate
    s = Regex.Replace(s, "/([A-Za-z][^/:]*)(?!<::)$", "/ns1:$1");                  // end
    s = Regex.Replace(s, "^([A-Za-z][^/:]*)$", "ns1:$1");                          // edge case
    s = Regex.Replace(s, "([-A-Za-z]+)\\(([^/:\\.,\\)]+)(?=[,\\)])", "$1(ns1:$2"); // xpath functions

    return s;
}

На самом деле это работает для простых случаев, которые я пробовал. Чтобы использовать приведенный выше пример - если вход является первым выражением xpath, я получаю вывод 2-й со всеми префиксами ns1. На самом деле вопрос в том, стоит ли надеяться, что этот подход Regex.Replace сработает, поскольку выражения xpath усложняются?

Ответы [ 4 ]

2 голосов
/ 01 апреля 2010

Если вы знаете, что существует только одно пространство имен (то есть пространство имен XHTML) и оно определено как пространство имен по умолчанию, то вы можете обмануть, обработав его с помощью XmlTextReader, который не поддерживает пространство имен, следующим образом:

            XmlTextReader tr = new XmlTextReader(new StringReader(@"<html xmlns=""http://www.w3.org/1999/xhtml"">
  <head>
    <title>Test</title>
  </head>
  <body>
    <h1>Example</h1>
  </body>
</html>"));
            tr.Namespaces = false;
            XPathDocument doc = new XPathDocument(tr);
            tr.Close();
            Console.WriteLine(doc.CreateNavigator().SelectSingleNode("html/body/h1").Value);

Это работает для меня и выводит «Пример», поэтому путь «html / body / h1» находит этот элемент «h1». Другие варианты - запустить ввод через некоторую таблицу стилей, чтобы сначала убрать пространства имен, а затем обработать результат преобразования с зачищенными пространствами имен.

И, конечно, вы можете подумать не полагаться на реализацию Microsoft XPath 1.0, а перейти на сторонние реализации XPath 2.0 или XQuery 1.0, например Saxon или XQSharp . Затем вы можете определить пространство имен элементов по умолчанию для выражений XPath или XQuery и использовать пути без префиксов для выбора элементов в пространстве имен XHTML.

2 голосов
/ 01 апреля 2010

Нет, спецификация XPath W3C явно говорит об этом :

"QName в тесте узла расширен в расширенное имя, используя объявления пространства имен от контекст выражения. Это тоже самое способ расширения для типа элемента имена в начальных и конечных тегах, кроме что пространство имен по умолчанию объявлено с xmlns не используется: если QName не имеет префикса, то URI пространства имен является нулевым (это так же, как имена атрибутов расширен). Это ошибка, если QName имеет префикс, для которого нет объявление пространства имен в контекст выражения. "

Любая попытка динамически «массировать» заранее неизвестное выражение XPath для достижения успеха в этой ситуации обычно заканчивается неудачей , что означает, что нужно иметь возможность выполнить полный анализ Выражение XPath и выделение всех имен элементов, которые не являются осями, именами операторов или функций - это не то, что я бы попросил любого делать Может даже быть чрезвычайно трудно правильно найти начало каждого шага местоположения (оператор "/"), потому что строка "/" может быть частью литерального строкового выражения.

Один из примеров (под) выражения, которое требует полного разбора:

div div div

Если к выражениям XPath будут наложены некоторые ограничения, то такой подход может иметь некоторый ограниченный успех, но, опять же, я не буду советовать, потому что никто не может доказать , что RegExes действительно работать правильно во всех случаях.

1 голос
/ 02 февраля 2012

Вот дешевый и быстрый способ взлома, который полностью устраняет пространство имен по умолчанию: измените имя атрибута xmlns.

например. если у вас уже есть xml в строковой переменной, сделайте это до фактического создания XPathDocument:

xml = xml.Replace(" xmlns="," xxxxx=");

(Потребовалось 0,00065 секунд для файла размером 93 КБ на моей машине.)

Тогда вы можете свободно использовать замечательные XPath-запросы без префиксов.

0 голосов
/ 09 января 2013

Я уже давно использую взлом регулярных выражений пространства имен по умолчанию, и, похоже, он работает нормально Я нашел оригинал на Stackoverflow и добавил несколько модификаций:

    s = Regex.Replace(s, "^(?!(::|([A-Za-z][-A-Za-z]+\\(.+\\))))([^/:]+)(?=/)", prefix + ":$1");                             // beginning
    s = Regex.Replace(s, "/([^\\.^@^/:\\*\\(]+)(?=[/\\[])", "/" + prefix + ":$1"); //segment with fixed attribute
    s = Regex.Replace(s, "(child|descendant|ancestor|ancestor-or-self|descendant-or-self|self|parent|following|following-sibling|preceding|preceding-sibling)::((?!([\\w]*\\(\\)))[A-Za-z][^/:*]*)((?=/)|(?=\\b))", "$1::" + prefix + ":$2");                  // axis specifier
    s = Regex.Replace(s, "\\[([A-Za-z][^/:*\\(]*)(?=[\\[\\]])", "[" + prefix + ":$1");        // within predicate
    s = Regex.Replace(s, "/([A-Za-z][^/:\\*\\(]*)(?!<::)$", "/" + prefix + ":$1");               // end
    s = Regex.Replace(s, "^([A-Za-z][^/:]*)$", prefix + ":$1");                               // edge case
    s = Regex.Replace(s, "([A-Za-z][-A-Za-z]+)\\(([^\\.^@^/:\\.,\\(\\)]+)(?=[,\\)])", "$1(" + prefix + ":$2"); // xpath functions with fixed attributes
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...