Запросы XML-документа

Один способ найти элементы интереса к XML-документу состоит в том, чтобы использовать методы NSXML, пересекающие узлы в дереве документов (см. Пересечение дерева XML). Однако это может быть длительным подходом, особенно с большими документами. Более эффективная стратегия состоит в том, чтобы выполнить XPath и запросы XQuery на объекте документа или любом из узлов в документе. Эта статья описывает основные шаги для выполнения XQuery, и XPath запрашивает и предлагает, как можно интегрировать такие запросы с пользовательским интерфейсом приложения.

XPath и основы XQuery

XQuery 1.0 и XPath 2.0 являются языками запросов, так хорошо интегрированными, что просто думать о них как об одном языке. Их формальная семантика, модель данных, и функции и операторы определяются в тех же спецификациях W3C (см., См. Также во введении). Однако, существуют различия, основное, являющееся, что XPath использует синтаксис пути стиля POSIX для описания расположений узлов в дереве XML. Кроме того, в то время как XQuery имеет дело с узлами и атомарными значениями, XPath имеет дело с узлами.

Класс NSXMLNode имеет два связанных с запросом метода, один для того, чтобы сделать запросы XPath и другой для того, чтобы сделать запросы XQuery:

Оба метода возвращают массив найденных элементов — соответствие последовательности в модели данных — но природа элементов отличается, и это различие отражается в именах методов. nodesForXPath:error: метод возвращает массив объектов NSXMLNode в то время как objectsForXQuery:constants:error: метод возвращает массив, потенциально содержащий и объекты NSXMLNode и объекты Основы, соответствующие атомарным типам (NSNumber, NSString, NSCalendarDate, и т.д.). Даже если эти методы находят только один элемент, они возвращают его в массиве.

Оба метода также запускают запрос в отношении начального узла контекста. Узел контекста является узлом, против которого применяется оценка пути расположения или другого выражения. В обоих методах начальный узел контекста является получателем сообщения. В выражении запроса можно также обратиться к узлу контекста с периодом (например, “.//products”).

Вы часто начинаете выражение XPath с двойной наклонной черты (“//”) сопровождаемый именем элемента. Это дает Вам все происходящие элементы узла контекста с тем именем, независимо от их уровня в иерархии. (Можно также использовать двойную наклонную черту в другом месте по пути.) Единственные наклонные черты, сопровождаемые именем элемента, указывают исключительный путь вниз древовидная иерархия. Предикаты являются булевыми тестами в квадратных скобках, выбирающих подмножество узлов, как оценено к той точке. Числа в квадратных скобках после элементов идентифицируют определенные дочерние узлы своим индексом (который в этом случае на основе 1). Для наблюдения этого на практике рассмотрите следующее выражение пути:

.//part/chapter[1]/section[@title=”Path Expressions”]

Оценка выражения пути работает слева направо. Это выражение сначала получает все названные элементы part и от этого выбирает первый названный элемент chapter; от этого это получает все дочерние названные элементы section, и от той последовательности это возвращает элемент раздела, заголовок которого является “Выражениями Пути”. XPath также позволяет Вам найти атрибуты по имени при помощи знака at-sign префикс. Например, следующее выражение пути получает дату модификации (атрибут) первой главы:

.//part/chapter[1]/@modDate

XPath также имеет подобные функции спецификаторы типа, позволяющие Вам обратиться к дочерним узлам кроме элементов и атрибутов, включая текстовые узлы (text()), обработка инструкций (processing-instruction()), и комментарии (comment()). Если родитель имеет больше чем один узел данного типа, все возвращаются.

Перечисление 1 является фрагментом кода, иллюстрирующего, как сделать использование запроса XPath nodesForXPath:error:. Это ссылается на a city элемент, который является дочерним элементом address элемент, который является третьим дочерним названным элементом person из узла контекста. Если существуют многократные названные элементы city в конце этого пути все они возвращаются.

Перечисление 1  , Выполняющее запрос XPath

NSError *err=nil;
NSXMLElement *thisCity;
NSArray *nodes = [theDocument nodesForXPath:@"./person[3]/address/city"
        error:&err];
if ([nodes count] > 0 ) {
    thisCity = [nodes objectAtIndex:0];
    // do something with element
}
if (err != nil) {
    [self handleError:err];
}

Начальный узел контекста для этого запроса, объект NSXMLDocument (theDocument), получатель сообщения. XPath возвращает узел или узлы, которые это находит в массиве (последовательность). Код в Перечислении 1 интересуется только извлечением первого узла в массиве, который это знает, чтобы быть объектом NSXMLElement. (Другие виды дочерних узлов могут также быть возвращены.), Если XPath испытывает затруднения при обработке строки запроса — например, запрос имеет синтаксическую ошибку — это непосредственно возвращается nil и косвенно возвращает объект NSError. Этот код просто передает тот объект другому методу для обработки.

Класс NSXMLNode определяет XPath метод, который может быть довольно полезным при создании запросов XPath. Как имя предполагает, можно отправить XPath обменивайтесь сообщениями к любому объекту узла для получения строки XPath, описывающей что расположение узла в дереве. Можно отправить или кэшировать эту строку так, чтобы узел мог легко быть получен позже через запрос XPath. Один возможный сценарий - то, что, когда узел изменяет свое местоположение в дереве, можно широковещательно передать уведомление, содержащее новое расположение как строку XPath в userInfo словарь.

XQuery является гибким и мощным языком запросов, охватывающим XPath. XQuery позволяет Вам составить логически сложные запросы с помощью операторов, кванторов, функций и выражений FLOWR (относящийся к ключевым словам for, let, order by, where, и return). С XQuery можно сортировать возвращенные значения, создать узлы, выполнить соединения, инвертировать иерархии, и динамично создать новые XML-документы.

Как пример, считайте простой запрос показанным в Перечислении 2.

Перечисление 2  простой запрос XQuery

for $p in .//person
where $p/address/zip_code > 90000
order by $p/last_name
return $p

Этот запрос циклы через каждый происходящий элемент узла контекста называют person и оценивает ли дочерний элемент в пути /address/zip_code имеет значение, больше, чем 90 000. Это сортирует элементы, удовлетворяющие этот тест значением их last_name дочерний элемент и возвраты получающаяся последовательность.

Выполнение запроса XQuery в NSXML является в основном вопросом передачи в строке запроса при вызове objectsForXQuery:constants:error: метод. Метод в качестве примера в Перечислении 3 получает строку от текстового представления в пользовательском интерфейсе.

Перечисление 3  , Выполняющее запрос XQuery

- (IBAction)applyXQuery:(id)sender {
    if (document) {
        NSError *error;
        NSArray *result = [document objectsForXQuery:
            [xquerySourceTextView string] constants:nil error:&error];
        if (result) {
            unsigned count = [result count];
            unsigned i;
            NSMutableString *stringResult = [[NSMutableString alloc] init];
            for (i = 0; i < count; i++) {
                [stringResult appendString:
                    [NSString stringWithFormat:@"%d: {\r", i]];
                [stringResult appendString:[[result objectAtIndex:i]
                    description]];
                [stringResult appendString:@"\r}\r"];
            }
            [xqueryResultTextView setString:stringResult];
            [stringResult release];
        } else if (error) {
            [self handleError:error];
        }
    }
}

Этот метод применяет предоставленный пользователями запрос и форматирует результаты прежде, чем вывести на экран его в другом текстовом представлении. Если существует ошибка при обработке запроса, она выводит на экран информацию об ошибке в предупредительной панели.

Второй параметр objectsForXQuery:constants:error: берет объект NSDictionary, ключи которого являются именем переменных, определенных как внешним. Значение такого ключа присваивается как значение переменной, когда это используется в запросе. Через этот механизм словарь констант позволяет Вам снова использовать строку запроса, содержащую переменную, значение которой может измениться для каждого отдельного выполнения запроса. Для больше о словаре констант и его потенциальном использовании для пользовательского интерфейса, посмотрите следующий раздел.

Интеграция XQuery в пользовательский интерфейс

Несмотря на то, что можно использовать XPath и XQuery в приложении, Вы не можете ожидать, что пользователи будут знать достаточно об этих языках запросов для построения их собственных запросов. Для использования этих языков эффективно в приложении необходимо найти способ включить намерение пользователя в каждый запрос. Существуют различные способы, Вы могли пойти об этом. Два из них обсуждены здесь, словарь констант и отформатированные строки.

Словарь констант

Словарь констант, представленный в XPath и Основах XQuery, позволяет Вам присваивать значения внешним переменным в запросе XQuery. Вы объявляете переменные в Прологе запроса и ссылаетесь на переменные при необходимости в запросе. (Пролог является серией объявлений и импорта, который создает среду для обработки запроса; это включает такие вещи как определения переменной, объявления модуля и импорт схемы.) Запрос может быть снова использован так много раз, как Вы хотите. Тогда Вы создаете словарь, содержащий пары ключ/значение, где ключ является именем переменной, и значение - то, чем Вы хотите, чтобы он был. Значение может быть получено непосредственно на объект пользовательского интерфейса.

Со словарем констант у Вас может, например, быть запрос, ищущий предоставленные пользователями условия из онлайнового словаря. Рисунок 1 дает простой пример пользовательского интерфейса для этого запроса.

  Простой пользовательский интерфейс рисунка 1 для запроса терминологии
Simple user interface for a terminology query

Со словарем констант можно присвоить значение термина поле к внешней переменной в строке запроса.

Скажем, объект контроллера подключает текстовое поле к названному выходу termField и подключает кнопку к методу действия. Следующие шаги показывают, как Вы могли бы (в методе действия), интегрируют термин переменная в строку запроса:

  1. Поместите следующую строку в Пролог запроса:

    declare variable $term as xs:string external;
  2. Вставьте переменную ($term) в запросе, где Вы хотите, чтобы значение использовалось или оценивалось.

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

    NSDictionary *dict = [NSDictionary dictionaryWithObject:[termField stringValue] forKey:@”term”];
  4. Отправьте objectsForXQuery:constants:error: метод к узлу контекста, передающему в словаре:

    NSArray *result = [document objectsForXQuery:queryString constants:dict error:&err];

Следует иметь в виду, что внешние переменные присвоили значения, подобные следующему выражению:

let $term := “a value”

Внешние переменные не походят на макросы, где значением заменяют заполнителя. Следовательно, Вы не можете использовать механизм словаря констант, чтобы сделать вещи, такие как динамичное изменение функций XQuery, используемых в строке запроса.

Отформатированные строки

stringWithFormat: метод класса NSString является мощным инструментом. С ним можно составить строки, компоненты которых могут варьироваться из-за внешних факторов, такой, как введено от пользователей. Можно использовать формат строки запроса XQuery таким же образом. И в отличие от внешних переменных, значения которых присваиваются из словаря констант, значениями «var-args» в отформатированной строке заменяют их заполнителей. Таким образом отформатированные строки позволяют Вам изменить связанные с языком части строки запроса динамично, включая операторов, имена функций и выражения пути.

Чтобы видеть, как это могло бы работать, это помогает последовать простому примеру. Рисунок 2 показывает возможный пользовательский интерфейс для выполнения запроса XQuery на XML-документе.

  Пользовательский интерфейс рисунка 2 для более сложного запроса
User interface for a more complex query

Выбор и записи, которые пользователь вносит с раскрывающимися списками и текстовым полем, становятся частью запроса, выполняемого, когда пользователь нажимает кнопку Find. Для определенного выбора и записи в примере выше, строка запроса становится следующим:

for $p in //person
where starts-with($p/phone/text(), “(408)”)
order by $p/lastName
return $p

Для раскрывающихся списков объединенные значения “представлены объекты”, связанные с элементами (объекты NSMenuItem) списков. Представленные объекты элементов первого списка являются выражениями пути XPath относительно узла контекста; представленные объекты элементов второго списка являются или операторами или именами функций XQuery. Перечисление 4 является методом, динамично создающим элементы второго раскрывающегося списка и устанавливающим их представленные объекты.

Перечисление 4  , Устанавливающее представленные объекты для раскрывающихся элементов списка

- (IBAction)changeOperationsList:(id)sender {
    [operationPopUp removeAllItems];
 
    if ([elementPopUp indexOfSelectedItem] > 4) { // operators
        NSMenuItem *anItem;
        [operationPopUp addItemsWithTitles:[NSArray arrayWithObjects:
            @"equals", @"greater than", @"less than", nil]];
 
        anItem = (NSMenuItem *)[operationPopUp itemAtIndex:0];
        if (anItem) {
            [anItem setRepresentedObject:@"="];
        }
        anItem = (NSMenuItem *)[operationPopUp itemAtIndex:1];
        if (anItem) {
            [anItem setRepresentedObject:@">"];
        }
        // continued ...
 
    } else { // functions
        NSMenuItem *anItem;
        [operationPopUp addItemsWithTitles:[NSArray arrayWithObjects:
            @"is", @"contains", @"begins with", @"ends with", nil]];
        anItem = (NSMenuItem *)[operationPopUp itemAtIndex:0];
        if (anItem) {
            [anItem setRepresentedObject:@"matches"];
            [anItem setTag:XQFunction];
        }
        anItem = (NSMenuItem *)[operationPopUp itemAtIndex:1];
        if (anItem) {
            [anItem setRepresentedObject:@"contains"];
            [anItem setTag:XQFunction];
        }
        // continued ...
    }
}

Когда пользователь выбирает элементы раскрывающегося списка, вводит значение для сравнения в текстовом поле и нажимает кнопку Find, метод действия, показанный в Перечислении 5, вызывается. Этот метод составляет строку запроса — по-другому, согласно тому, требуются ли функция или оператор — от представленных объектов и значения текстового поля. Это тогда отправляет objectsForXQuery:constants:error: обменивайтесь сообщениями к узлу контекста (объект документа) для выполнения запроса.

Перечисление 5  , Сочиняющее и выполняющее запрос

- (IBAction)findByXQuery:(id)sender {
 
    NSString *queryString;
    NSArray *results;
    NSError *err=nil;
    [queryStatus setStringValue:@""];
    if ([[operationPopUp selectedItem] tag] == XQFunction ) {
        queryString = @" \
            for $p in //person \
            where $op($p/%@/text(), $query) \
            order by $p/lastName \
            return $p",
            [[operationPopUp selectedItem] representedObject],
            [[elementPopUp selectedItem] representedObject]];
    } else {
        queryString = [NSString stringWithFormat:@" \
            for $p in //person \
            where $p/%@/text() %@ \"%@\" \
            order by $p/lastName \
            return $p",
            [[elementPopUp selectedItem] representedObject],
            [[operationPopUp selectedItem] representedObject],
    }
    results = [xmlDoc objectsForXQuery:queryString constants:[NSDictionary dictionaryWithObject:[queryValue stringValue] forKey:@"query"] error:&err];
    if (results && [results count] > 0) {
        [self doSomethingWithQueryResults:results];
    } else {
        [queryStatus setStringValue:[NSString stringWithFormat:
            @"No records found, query errors: %@",
            (err ? [err localizedDescription] : @"None")]];
    }
}

Если какое-либо из полей будет содержать злонамеренный ввод, то строка XQuery станет недопустимой или управляемой в выполнение чего-то, что это не должно. В этом случае, [queryValue stringValue] прибывает непосредственно от пользователя. Например, существует функция XPath для загрузки любого локального или удаленного XML-документа и сделать дальнейшие запросы против него. Эта атака подобна инжекции SQL, и мы защищаем от него путем усиления безопасных констант: параметр, предоставленный Какао в objectsForXQuery:constants:error: метод.

Вместо того, чтобы выбрать строковое значение queryValue поле этот путь, Вы могли объявить внешнюю переменную в Прологе запроса, поместить значение queryValue поле в словаре констант и ссылка переменная в запросе. См. Словарь Констант для подробных данных об этом подходе.

Ресурсы для изучения XQuery

XQuery является довольно сложным языком, и несмотря на то, что этот документ суммирует существенные аспекты синтаксиса и поведения, это не делает попытку полного описания XQuery. К счастью, превосходные онлайновые и загружаемые учебные руководства могут быть найдены посредством простых поисков в Интернете.