Оригиналы статей

  • http://www.xml.com/lpt/a/2003/04/30/editing.html
  • http://www.xml.com/lpt/a/2003/06/25/editing.html

     

    Редактирование XML-данных через Web при помощи W3C XML Schema и XSLT
    Автор - Ali Mesbah

    W3C XML Schema (WXS) - основанный на XML язык для описания и конструирования содержимого XML-документов. Стандартной практикой становится использование WXS для валидации данных в XML-документах. Также при помощи схем мы можем выполнять смежную задачу: создавать или редактировать корректное содержание XML-документа, используя информацию из схемы.

    В этой статье описано, как содержимое XML-документа может быть отредактировано через автоматически созданный основанный на формах GUI, причем процесс основывается на схеме данных документа. Здесь представлен полный цикл создания GUI (с использованием XSLT), редактирования и обновления (с использованием XUpdate) XML-данных.

    Идея в целом

    Следующая схема иллюстрирует общую идею: содержимое XML-документа, которое можно редактировать через GUI, созданный по соответствующей схеме.

    Идея в целом
    Схема 1. Предполагаемая схема системы.

    Преобразование схем в GUI

    Первое, о чем следует подумать, это прямая трансформация схемы через процессор (например, XSL-процессор) в GUI.

    Прямая трансформация XMLSchema в основанный на формах GUI
    Схема 2. Прямая трансформация XMLSchema в основанный на формах GUI

    Схема (помечена как XSD) преобразована через процессор в GUI. Процессор создает поле ввода для каждого (простого) элемента, встречающегося в XSD-документе. Возможный подход к такому варианту действий был изложен в статье Transforming XML Schemas, в которой рассматривалось преобразование XSD в основанный на формах GUI с использованием XSLT.

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

    Ограниченность этого варианта проявляется в том, что он работает только при создании нового XML-документа с данными, и процессор только трансформирует схему в GUI. Уже существующие XML-данные нельзя редактировать, поскольку процессор не знает о трансформации XML-данных в GUI.

    Редактирование XML-данных

    Нам требуется процессор, способный взять XML-документ с данными и создать основанный на формах GUI, чтобы можно было редактировать документ. См. следующую схему.

    Редактирование XML-данных
    >Схема 3. Редактирование XML-данных.

    На схеме 3 отражена следующая концепция: вместо трансформации схемы прямо в GUI, процессор сделает XSLT-файл, который мы назовем XSLGUI. Эта стилевая таблица содержит информацию о том, как именно трансформировать XML-данные корректно при помощи схемы в основанный на формах GUI.

    Чтобы понять на примере, как работает эта концепция, представим, что у нас есть схема Person.xsd, определяющая элементы, описывающие личность человека (нпр., name, last_name, ...):

    <?xml version="1.0" encoding="UTF-8"?>
    <xsd:schema version="1.0" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    
      <xsd:element name="person">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="name" type="xsd:string"/>
            <xsd:element name="phone" type="xsd:string" 
                minOccurs="1" maxOccurs="3"/>
            <xsd:element name="date_of_birth" 
                type="xsd:date"/>
            <xsd:element name="course" minOccurs="0" 
                maxOccurs="unbounded">
              <xsd:complexType>
                <xsd:sequence>
                  <xsd:element name="course_name" 
                      type="xsd:string"/>
                  <xsd:element name="start_date" 
                      type="xsd:date"/>
                </xsd:sequence>
              </xsd:complexType>
            </xsd:element>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>

    Представим также, что у нас также есть документ с данными, описывающими человека по имени Марк (Marc.xml), и этот документ использует Person.xsd:

    <?xml version="1.0" encoding="UTF-8"?>
    <person 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="Person.xsd">
    
      <name>Marc</name>
      <phone>0153816546</phone>
      <phone>0630458920</phone>
      <date_of_birth>1978-01-21</date_of_birth>
      <course>
          <course_name>TCP/IP</course_name>
          <start_date>2001-12-10</start_date>
      </course>
      <course>
          <course_name>Java Programming</course_name>
          <start_date>1997-08-01</start_date>
      </course>
    </person>

    Цикл, показанный на схеме 3, использует Person.xsd для создания XSLT-файла (XSLGUI), который способен создать GUI из элементов, найденных в Marc.xml.

    Существует много способов, как XSLGUI может сделать основанный на формах GUI из файла с исходными данными. Первый способ - использовать XForms, имеющий сейчас статус кандидата в рекомендации W3C. XSLGUI должен в этом случае создать правильный XML-документ XForms, основанный на XML-документе с данными (e.g Marc.xml). Однако использование XForms означает, что броузер должен поддерживать XForms, и т.к. XForms еще не является рекомендацией, наиболее распространенные броузеры его не используют.

    Второй способ - Apache Cocoon XMLForm. Основное отличие между Cocoon XMLForm и W3C XForms в том, что XMLForm можно использовать в любом броузере на любом клиентском устройстве, в то время как Cocoon не требует, чтобы на стороне клиента распознавалась XForms-разметка (см. XML Forms, Web Services and Apache Cocoon).

    Возможный третий способ, представленный в этой статье, есть комбинация XPath, XUpdate и W3C XML Schema validation. Причина, по которой мы выбрали этот способ, в том, что он не зависит от броузера (броузеры пока еще не поддерживают XForms) или от использования Cocoon (если вы выбираете XMLForms, вы должны использовать Cocoon).

    Автор - Ali Mesbah

    Для каждого (простого) элемента в Marc.xml (нпр., name, last_name, ...) XSLGUI создаст поле ввода. В основанном на броузере GUI поля ввода имеют атрибуты name и value, и только эти два параметра мы можем послать нашему сервлету. Следовательно, XSLGUI устанавлиает параметр name для каждого элемента формы, который соответствует XPath-позиции этого элемента в XML-документе, в порядке обработки структуры данных в этом документе. Например, HTML-тег input для второго телефонного номера в нашем документе Marc.xml будет выглядеть так:

    <input name="/person/phone[2]" value="0630458920"/>

    Согласно документу W3C "Basic HTML data types", атрибут name тега input должен начинаться с буквы, поэтому строку "/person/phone[2]" недопустимо использовать как значение атрибута name тега input. Мы протестировали эту систему записи в сервлете под Tomcat, и она работает. Однако можно заменить симол "/" на какой-нибудь другой допустимый символ, а на серверной стороне провести обратное преобразование в корректный XPath.

    В процессе создания этого XPath необходимо, чтобы XSLGUI отслежиал путь к местонахождению и числу элементов в нашем XML-документе. Далее показаны части нашего сгенеренного XSLGUI. Для всех некомплексных элементов создается match (???), подобный аналогичному для "phone". Строка пути, показывающая позицию текущего элемента в каждом match, приводится, если надо, в стандартный вид и обрабатывается рекурсивно. Число вхождений каждого элемента также подсчитывается и используется как индекс в XPath-нотации. Для course, являющегося complexType-элементом, напрямую полей ввода не создается, но параметр path передается его потомкам при вызове apply-template.

    Ниже - краткое представление документа XSLGUI:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="person">
        <xsl:param name="root">/person</xsl:param>
        <html>
        <head>
        <title>person</title>
        </head>
        <body>
        <form name="xmsForm" action="xms.InputPage" method="post">
            <xsl:apply-templates>
              <xsl:with-param name="path" select="$root"/>
            </xsl:apply-templates>
            <input type="submit" name="action" value="save"/>
        </form>
        </body>
        </html>
      </xsl:template>
      ...
      <xsl:template match="phone">
        <xsl:param name="path"/>
        <xsl:variable name="index">
          <xsl:number count="phone"/>
        </xsl:variable>
        <B>phone: </B>
        <!--  Нижеследующее выводится так:
          <input name="/person/phone[2]" value="0630458920"/> -->
        <input>
          <xsl:attribute name="name">
            <xsl:value-of 
              select="concat($path,'/phone[', $index,']')"/>
          </xsl:attribute>
          <xsl:attribute name="value">
            <xsl:value-of select="text()"/>
          </xsl:attribute>
        </input>
      </xsl:template>
      ...
      <xsl:template match="course">
        <xsl:param name="path"/>
        <xsl:variable name="index">
          <xsl:number count="course"/>
        </xsl:variable>
        <B>course </B>
        <xsl:apply-templates select="*">
          <xsl:with-param name="path"
            select="concat($path,'/course[', $index,']')"/>
        </xsl:apply-templates>
      </xsl:template>
        ...
    </xsl:stylesheet>

    Как мы видим, каждое создаваемое поле ввода в качестве "имени" использует свою XPath-адресацию, которая описывает точную позиию этого элемента в XML-документе с данными. Представим, что пользовватель изменяет значение второго телефонного номера с 0630458920 на 0150458920. Когда пользователь запрашивает сохранение данных, сервлет получает введенную информацию.Теперь он должен пройти в цикле по всем веденным полям, чьи имена - корректно записанные XPath-выражения, и создать XUpdate-документ. Для второго телефонного номера серер получает параметр с name="/person/phone[2]" и value="0150458920"

    XUpdate-документ с командой update может быть таким:

    <?xml version="1.0" encoding="UTF-8"?>
    <xu:modifications 
      xmlns:xu="http://www.xmldb.org/xupdate">
      <xu:update select="/person/phone[2]">
        0150458920
      </xu:update>
    </xu:modifications>

    После того, как для всех введенных данных будет создан XUpdate-документ, копия исходного документа Marc.xml будет использована для обновления исходного документа при помощи JAXUP, который определяет интерфес обновления XML-документов и обеспечивает реализациюXUpdate-спецификации, предложеной инициативной группой XML:DB.

    После этого проводится проверка копии на корректность (валидация). Если документ валиден, он сохраняется на место оригинального, в противном случае пользователю возвращаются ошибки.

    Процессор

    Мы уже обсуждали процессор, который берет WXS-схему и производит требуемую трансформацию в "XSLGUI". Процессор может быть каким угодно, лишь бы он был в состоянии выдавать необходимую информацию. Например, комбинация DOM и Java может быть процессором. В нашем случае процессор - фактически комбинация XSLT-документов и преобразований, как видно из следующей схемы.

    Процессор
    Схема 5. XML Schema для процессора XSLGUI.

    Все, что нам нужно - это XSLT-стиль, который берет наш XSD и трансформирует его в соответствующий XSLGUI. Назовем этот стиль "MetaXSLGUI".

    Основная задача MetaXSLGUI состоит в следующем:

    1. Для каждого элемента создать xsl:template с атрибутом match, эквивалентным имени элемента.

    2. Если это complexType-элемент, то добавить в шаблон функцию xsl:apply-templates select="*" с соответствующими параметрами.

    3. Добавить правильные XSLT-функции и элементы в xsl:template из шага 1 так, чтобы xsl:template при необходимости мог создать необходимые элементы для ввода.

    Этот пример кода показывает, как может выглядеть возможный MetaXSLGUI. В W3C XML Schema есть много возможностей, и хотя достаточно сложно создать стиль, который учитывает все эти возможности, теоретически это возможно. Это всего лишь вопрос времени, которое уйдет на создание стиля с такой функциональностью. Мы надеемся, что этот простой MetaXSLGUI может послужить началом движения в этом направлении.

    Заключение

    В этой статье была кратко описана новая перспектива создания основанного на формах GUI, использующая XML Schema и единственный XSLT-стиль, при помощи которых возможно редактирование XML-данных. Конечно, некоторые темы остались открытыми, например, как добавить новый элемент в документ с данными, как инициировать минимальную структуру данных по схеме. Эти вопросы будут обсуждаться в одной из следующих статей.

    Дополнительная информация

     

    Редактирование XML-данных через Web при помощи W3C XML Schema и XSLT, часть 2
    Авторы - Ali Mesbah, Arjan Vermeij

    Вставка элементов

    В предыдущей статье мы обсуждали способ автоматической генерации основанного на формах GUI с использованием XML Schema -- способ, который использует только один XSLT-стиль, с помощью которого становится возможным редактирование XML-данных. Остались открытыми такие вопросы, как добавление нового элемента в документ с данными, а также создание (инициализация) первичной структуры данных по нашей схеме.

    Один из самых распространенных способов использования схем - проверка XML-документа на корректность по определенным правилам. Схемы также используют для того, чтобы задать порядок следования элементов или их тип. Ткже мы можем определить приоритет, важность элементов в схеме. Эту информацию можно использовать для того, чтобы вставлять или удалять элементы документа, сохраняя при этом его корректность.

    В этой статье описывается способ вставки элементов в xML-документ через автоматически сгенеренный основанный на формах GUI, использующий XML Schema документа данных.

    Редактирование XML-документа

    В предыдущей статье была описана концепция редактироания существующего документа с использованием соответствующей ему схемы. Эта концепция представлена на следующей схеме.

    Редактирование XML-данных
    Схема 1. Редактирование XML-данных

    В этой концепции значения элементов документа можно редактировать, но она не обеспечивает способа вставить или удалить элемент документа.

    Рассмотрим предыдущий пример (с некоторыми изменениями, выделенными жирным шрифтом) схемы (ранее помеченной как XSD) Person.xsd, в которой мы определили элементы, описыващие личность, плюс вложенность (важность? обязательность?) каждого элемента:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsd:schema version="1.0" 
         xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <xsd:element name="person">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="name" 
                            type="xsd:string"/>
            <xsd:element name="phone" 
                            type="xsd:string" 
                            maxOccurs="3"/>
            <xsd:element name="date_of_birth" 
                            type="xsd:date"/>
            <xsd:element name="course" 
                            minOccurs="0" 
                            maxOccurs="3">
              <xsd:complexType>
                <xsd:sequence>
                  <xsd:element name="course_name" 
                                  type="xsd:string"/>
    <xsd:element name="course_code" 
                                  type="xsd:string" 
                                  minOccurs="0"/>
                </xsd:sequence>
              </xsd:complexType>
            </xsd:element> 
          </xsd:sequence>  
        </xsd:complexType>
      </xsd:element>      
    </xsd:schema>

    Представим также, что мы уже опять создали документ, описывающий воображаемую персону по имени Marc (Marc.xml) при помощи Person.xsd:

    <?xml version="1.0" encoding="UTF-8"?>
    <person 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="Person.xsd">
            
      <name>Marc</name>
      <phone>
        0153816546
      </phone>
      <phone>
        0630458920
      </phone>
      <date_of_birth>
        1978-01-21
      </date_of_birth>
      <course>
          <course_name>
            TCP/IP
          </course_name>
          <course_code>
            T465
          </course_code>
      </course>
      <course>
          <course_name>
            Java Programming
          </course_name>
          <course_code>
            J867
          </course_code>
      </course>    
    </person>

    Вставка элементов, появляющихся в документе с данными

    Допустим, у нас есть документ с данными, в котором содержатся несколько элементов одного типа. Мы хотим вставить еще один элемент этого типа в документ. Общая идея вставки документа через GUI описывается так:

    Теперь, используя пример, мы можем выработать концепию, которая может применяться с использованием XSLT и XML Schema.

    В примере Person.xsd мы определили элемент course как имеющий параметр minOccurs, равный нулю, и параметр maxOccurs, равный 3. Т.о., в Marc.xml элемент courses может встречаться 3 раза.

    Имея в схеме эту информацию о допустимом числе элементов, мы могли бы захотеть найти способ вставить элементы в XML-файл через GUI, сохранив при этом корректность документа с данными.

    Чтобы получить возможность вставлять элементы, мы будем использовать "кардинальность" (minOccurs и maxOccurs), определенную в XSD, и ставить знак "+" после каждого элемента, кардинальность которого выше, чем число элементов, представленных в документе с XML-данными. Например, для Marc.xml GUI поставит "+" после каждого course, потому что в Marc есть два course, а значение атрибута maxOccurs элемента course равно 3.

    Авторы - Ali Mesbah, Arjan Vermeij

    Первое, что мы должны сделать, это расширить MetaXSLGUI, представленный в предыдущей статье, таким образом, чтобы сгенеренный XSLGUI знал, где и когда ставить "+". Вот так может выглядеть XSLGUI после такого расширения:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:strip-space elements="*"/>
      ...
      <xsl:template match="course">
        <xsl:param name="path"/>
        <xsl:variable name="index">
          <xsl:number count="course"/>
        </xsl:variable>
    <xsl:variable name="maxOccurs" select="3"/>
        <xsl:variable name="nodeCount" 
                         select="count(../course)"/>
        <b>course </b>
    <xsl:if test="$nodeCount < $maxOccurs">
          <a>
            <xsl:attribute name="href">
              <xsl:value-of 
                select="concat('javascript:submitForm(',
                                "'",$path,
                                '/course[', $index,']',
                                "'", ',', 
                                "'",'insert',
                                "'",');')"/>
            </xsl:attribute>
            <xsl:attribute name="onClick">
              <xsl:value-of 
                select="concat('submitForm(',"'",
                                $path,'/course[', $index, 
                                ']', "'", ',', 
                                "'", 'insert', 
                                "'", '); 
                                return false;')"/>
            </xsl:attribute>
            +
          </a>
        </xsl:if>
        <xsl:apply-templates select="*">
          <xsl:with-param name="path" 
            select="concat($path,'/course[', $index,']')"/>
        </xsl:apply-templates>
      </xsl:template>
      ...
      <xsl:template match="phone">
        <xsl:param name="path"/>
        <xsl:variable name="index">
          <xsl:number count="phone"/>
        </xsl:variable>
    <xsl:variable name="maxOccurs" select="3"/>
        <xsl:variable name="nodeCount" 
          select="count(../phone)"/>
        <b>phone: </b>
        <!--  The output of the following will look like
                 this: <input name="/person/phone[2]" 
                            value="0630458920"/> 
        -->
        <input>
          <xsl:attribute name="name">
            <xsl:value-of 
              select="concat($path,'/phone[', 
                             $index,']')"/>
          </xsl:attribute>
          <xsl:attribute name="value">
            <xsl:value-of select="text()"/>
          </xsl:attribute>
        </input>
    <xsl:if test="$nodeCount < $maxOccurs">
          <!--  The output of the following will look 
                   like: 
                   <a href="javascript:submitForm(
                                        '/person/phone[2]',
                                        'insert');" 
                         onClick="submitForm('
                                  /person/phone[2]',
                                  'insert'); 
                                  return false;">
                                  +</a> 
          -->
          <a>
            <xsl:attribute name="href">
              <xsl:value-of 
                select="concat('javascript:submitForm(', 
                               "'",$path,
                               '/phone[', $index, ']',
                               "'", ',', 
                               "'", 'insert',
                               "'", ');')"/>
            </xsl:attribute>
            <xsl:attribute name="onClick">
              <xsl:value-of 
                select="concat('submitForm(',"'",
                               $path,'/phone[', $index,']',
                               "'", ',',
                               "'", 'insert',
                               "'",'); 
                               return false;')"/>
            </xsl:attribute>
            +
          </a>
        </xsl:if>
      </xsl:template>
      ...
    </xsl:stylesheet>

    Это - расширенный MetaXSLGUI.

    Значение атрибута maxOccurs в XSD используется в шаблоне элемента. При каждом прогоне (match) число элементов в данном месте документа подсчитывается и устанавливается значением nodeCount. Потом проверяется, действительно ли nodeCount меньше, чем атрибут maxOccurs элемента. Если это так, элемент получает свой плюс. После этого знак "+" залинковывается на ссылку, отправляющую нашу форму с соответствующими параметрами. (Мы могли бы использовать этот же прием для проставления минуса и удаления элемента из структуры данных.)

    0

    Когда пользователь кликает на знак "+" рядом со вторым course, на сервер отправляется запрос с XPath-позицией вставляемого элемента. Можно вставлять элемент до или после элемента с плюсом. В этом примере мы вставляем новый элемент перед элементом с плюсом. Используя нотацию XPath, мы знаем, что новый элемент course должен быть вставлен перед вторым course в элементе person. Сервер создает документ XUpdate с командой "вставки перед":

    <?xml version="1.0" encoding="UTF-8"?> 
    <xu:modifications 
           xmlns:xu="http://www.xmldb.org/xupdate">
      <xu:insert-before select="/person/course[2]">
        <insert-course/>
      </xu:insert-before>
    </xu:modifications>

    Мы вставляем не сам элемент course, а элемент insert-course. Причины этого в том, что на стороне сервера мы не знаем ничего о структуре вставляемого элемента. Все, что нам известно из пришедшего запроса, это то, что пользователь хочет вставить элемент course перед уже имеющимся вторым элементом course (/person/phone[2]).

    Этот XUpdate выполняется на временной копии исходного документа с данными (Marc.xml). Теперь у нас есть XML-документ (обозначим его как XML+) с дополнительным тегом, не определенным в нашем XSD, и являющимся просто дополнительным временным тегом:

    <?xml version="1.0" encoding="UTF-8"?>
    <person 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="Person.xsd">
            
      <name>Marc</name>
      <phone>0153816546</phone>
      <phone>0630458920</phone>
      <date_of_birth>1978-01-21</date_of_birth>
      <course>
        <course_name>TCP/IP</course_name>
        <course_code>T465</course_code>
      </course>
    <insert-course/>
      <course>
        <course_name>Java Programming</course_name>
        <course_code>J867</course_code>
      </course>    
    </person>

    См. следующий рисунок:

    Inserting elements

    Схема 2. Вставка элементов

    Чтобы получить возможность трансформировать XML+ в XML, необходимо иметь XSLT с информацией о том, как заменить тег <insert-elementName/> на тег элемента, который он представляет. Этот XSLT (назовем его XSL+) создан с использованием XSD. В этом XSLT для каждого элемента из XSD создан соответствующий match с элементом(ами), который должен заменить insert-тэг в XML+. В XSL+ есть функция подстановки insert-тэга с сохранением исходной структуры и значений прочих элементов.

    Ниже показаны части XSL+:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0"
       xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:strip-space elements="*"/>
      
      <xsl:template match="person">
        <xsl:element name="person">
          <xsl:apply-templates select="*"/>
        </xsl:element>
      </xsl:template>
      ...
      <xsl:template match="insert-phone">
        <xsl:element name="phone"/>
      </xsl:template>
      
      <xsl:template match="insert-course">
        <xsl:element name="course">
          <xsl:element name="course_name"/>
        </xsl:element>
      </xsl:template>
      
      <xsl:template match="insert-person">
        <xsl:element name="person">
          <xsl:element name="name"/>
          <xsl:element name="date_of_birth"/>
          <xsl:element name="phone"/>
        </xsl:element>
      </xsl:template>
      ...
    </xsl:stylesheet>

    XML+ с тегом insert-course будет трансформирован в корректный документ, отличающийся от исходного XML-документа только наличием дополнительного элемента course. Marc.xml будет выглядеть примерно так:

    <?xml version="1.0" encoding="UTF-8"?>
    <person 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="Person.xsd">
            
      <name>Marc</name>
      <phone>0153816546</phone>
      <phone>0630458920</phone>
      <date_of_birth>1978-01-21</date_of_birth>
      <course>
          <course_name>TCP/IP</course_name>
          <course_code>T465</course_code>
      </course>
      <course>
          <course_name/>
      </course>
      <course>
          <course_name>
            Java Programming
          </course_name>
          <course_code>
            J867
          </course_code>
      </course>    
    </person>

    В элемент course добавлен только элемент course_name. Это потому, что у course_name в схеме параметр minOccurs равен 1 (значение по умолчанию), а у course_code параметр minOccurs равен 0, что означает, что это не обязательный элемент. После проверки на корректность этот XML-документ может быть трансформирован в GUI с использованием XSLGUI, как и любой другой документ с данными.

    Авторы - Ali Mesbah, Arjan Vermeij

    MetaXSL+

    Все, что нам нужно - это XSLT, берущий наш XSD и преобразовывающий его в соответствующий XSL+. Здесь применяется та же концепция, что использовалась для создания XSLGUI. Стиль, генерящий XSL+, назовем MetaXSL+.

    Преобразование XML Schema в XSL+
    Схема 3. Преобразование XML Schema в XSL+

    Основная задача MetaXSL+ состоит в следующем:

    1. Для каждого элемента создать xsl:template с атрибутом match, эквивалентным имени элемента.
    2. Если это datatype-элемент, добавить его (???) и функции, необходимые для создания значения элемента, в xsl:template
    3. Если это complexType-элемент, добавить функцию xsl:apply-templates select="*"
    4. Для каждого элемента создать xsl:template с атрибутом match, равным insert-elementName, где elementName - название элемента.
    5. Если это complexType-элемент, добавить рекурсивно всех его потомков N раз, где N - значение атрибута minOccurs для элемента-потомка

    Создание новых документов с данными (инициализация)

    Как мы видим, XSL+ может трансформировать тэг insert-elementName в соответствующийй элемент с правильным содержимым. Теперь создание документа с данными по схеме Person.xsd есть не что иное как тег insert-person. Все, что нам нужно - это XSLT, который находит первый элемент в нашем XSD (person) и создает XML+, в котором содержится только одна строка, а именно:

    <insert-person>

    Это делает следующий XSLT:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <xsl:stylesheet version="1.0"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
         xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:template match="xsd:element">
    <xsl:variable name="inserttag" 
                     select="concat('insert-', @name)"/>
      <xsl:element name="{$inserttag}"/>
    </xsl:template>
    </xsl:stylesheet>

    Используя тот же цикл, что описан выше, этот новый XML+ трансформируется в документ с данными при помощи XSL+. Результатом будет следующий код:

    <xsl:element name="person">
      <xsl:element name="name"/>
      <xsl:element name="date_of_birth"/>
      <xsl:element name="phone"/>
    </xsl:element>

    Это - минимальный корректный документ, построенный по схеме Person.xsd.

    Вставка элементов, отсутствующих в минимальном корректном документе

    Теперь научимся добавлять элемент, отсутствующий минимальном корректном документе (т.е. элемент с "minOccurs=0"), например, элемент course в информации о новой личноссти, или элемент course_code в новом элементе course.

    Существуют разные варианты действий:

    1. При создании нового документа добавлять в этот документ все элементы, в том числе элементы, для которых minOccurs равен нулю. Это довольно простое решение, и хотя создаваемый в результате документ корректен, он не совсем отвечает нашим ожиданиям: пустой элемент не эквивалентен отсутствию элемента в документе.
    2. Откорректировать XSLGUI так, чтобы он генерил знак "+" после каждого элемента. В нашем примере с личностью мы бы получили один знак "+" для course после name, один - после date_of_birth, и один - после phone. В этом случае если пользователь кликает на любой из двух первых плюсов, элемент course будет вставлен на место, не разрешенное в XSD. Элемент insert-course после элемента phone будет корректным. Например, документ с большим числом элементов будет неудобен для пользователя, поскольку надо будет сделать слишком много попыток, чтобы найти корректное место вставки элемента.
    3. Найти способ проверки осмысленности и корректности установки плюсов, и передать эту информацию в XSLGUI. Концептуально это элегантное решение, но технически его довольно сложно реализовать.
    4. Еще одно решение - именно то, что и рассматривается в этой статье - состоит в том, чтобы проставлять плюсы наследникам complexType-элемента так, как описано ниже. Для всех наследников с minOccurs, равным 0, и если число вхождений элемента в документ равно нулю, создавать знак "+" на уровне complexType-элемента, содержащего наследников.

    В нашем примере person - complexType-элемент, и его наследник course имеет minOccurs равный 0. XSLGUI будет создан так:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0"
       xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="person">
        <xsl:param name="root">
          /person
        </xsl:param>
        <xsl:variable name="index">
          1
        </xsl:variable>
        <html>
        <head>
          <title>person</title>
        </head>
        <body>
        <form name="xmsForm" action="xms.InputPage"
                                method="post">
          
          <!-- The output of the following will look 
                  like:
                  <a href="javascript:submitForm(
                              '/person[1]/course', 
                              'insertfirstposition');"
                        onClick="submitForm(
                                '/person[1]/course',
                                'insertfirstposition'); 
                                return false;">
                                Add course +
                                </a>
          -->              
    <xsl:variable name="courseCount" 
                              select="count(course)"/>
          <xsl:if test="$courseCount='0'">
            <a>
              <xsl:attribute name="href">
                <xsl:value-of 
                  select="concat('javascript:submitForm(', 
                                  "'",'/person[',
                                  $index,']','/course',
                                  "'",',',
                                  "'",
                                  'insertfirstposition',
                                  "'",');')"/>
              </xsl:attribute>
              <xsl:attribute name="onClick">
                <xsl:value-of 
                  select="concat('submitForm(',
                                  "'",'/person[',
                                  $index,']','/course',
                                  "'",',',
                                  "'",
                                  'insertfirstposition',
                                  "'",'); 
                                  return false;')"/>
              </xsl:attribute>Add course +
            </a>
          </xsl:if>
          <xsl:apply-templates>
            <xsl:with-param name="path" 
                               select="$root"/>
          </xsl:apply-templates>
          <input type="submit" name="action" 
                                  value="save"/>
        </form>
        </body>
        </html>
      </xsl:template>
      ...
    </xsl:stylesheet>

    Теперь на уровне элемента person (верхний уровень документа), если пользователь кликает на знак "+" для вставки course, к серверу посылается запрос на действие insertfirstposition с XPath-параметром /person/course. Мы знаем, что вновь созданный документ не содержит элементов course (иначе у нас не было бы знака "+" для course), так что нет способа, которым мы могли бы определить, куда вставлять элемент course. Все, что нам известно, это то, что элемент должен быть вставлен в /person. Затем, начиная с первой позиции внутри /person, вставляем course и проверяем документ. Если результат корректен, используем XSLGUI для создания GUI; в противном случае вставляем элемент в следующую позицию, и так до тех пор, пока не найдем корректную позицию для course. Этот способ позволяет вставить элемент в первую допустимую позицию исходного документа. Заметим, что может быть предпочтительно вставить элемент не в первую допустимую позицию, а куда-то еще. Это может быть довольно сложной задачей.

    XUpdate используется для вставки нового элемента в разные позиции, как показано ниже:

       <?xml version="1.0" encoding="ISO-8859-1"?>
      <xu:modifications 
           xmlns:xu="http://www.xmldb.org/xupdate">
        <xu:append select="/person[1]" 
                      child=index>
           <xu:element name="insert-course"/>
        </xu:append>
      </xu:modifications>

    Здесь index инициализируется в 0 и возрастает до тех пор, пока не обнаруживается корректная позиция.

    Заключение

    В этой статье описан способ вставки элементов в XML-документ через автоматически генерящийся, основанный на формах GUI с использованием XML Schema документа с данными и XSLT. Возможность редактирования и вставки или удаления элементов с использованием соответствующего XML Schema обеспечивает полнофункциональный метод создания web-ориентированного XML-редактора.

    Дополнительная информация

    XML.com Copyright © 1998-2003 O'Reilly & Associates, Inc.

    Hosted by uCoz