Преобразование встроенного форматирования OOXML в объединенный элемент - PullRequest
6 голосов
/ 09 июня 2011

В OOXML форматирование, такое как полужирный, курсив и т. Д., Может быть (и часто досадно) разделено между несколькими элементами, например так:

<w:p>
    <w:r>
        <w:rPr>
            <w:b/>
         </w:rPr>
         <w:t xml:space="preserve">This is a </w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t xml:space="preserve">bold </w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
            <w:i/>
        </w:rPr>
        <w:t>with a bit of italic</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t xml:space="preserve"> </w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t>paragr</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t>a</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t>ph</w:t>
    </w:r>
    <w:r>
        <w:t xml:space="preserve"> with some non-bold in it too.</w:t>
    </w:r>
</w:p>

Мне нужно объединить эти элементы форматирования для полученияthis:

<p><b>This is a mostly bold <i>with a bit of italic</i> paragraph</b> with some non-bold in it too.</p>

Мой первоначальный подход состоял в том, чтобы выписать начальный тег форматирования при его первом обращении, используя:

 <xsl:text disable-output-escaping="yes">&lt;b&gt;</xsl:text>

И затем после того, как я обработаю каждый <w:r>проверьте следующий, чтобы увидеть, если форматирование все еще присутствует.Если это не так, добавьте конечный тег таким же образом, как я добавляю начальный тег.Я продолжаю думать, что должен быть лучший способ сделать это, и я был бы благодарен за любые предложения.

Следует также упомянуть, что я связан с XSLT 1.0.

Причина, по которой это необходимо, заключается в том, что нам необходимо сравнить файл XML до его преобразования в OOXML и после его преобразования.из OOXML.Благодаря дополнительным тегам форматирования создается впечатление, что изменения были сделаны, когда их не было.

Ответы [ 4 ]

6 голосов
/ 16 июня 2011

Вот полное решение XSLT 1.0 :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" xmlns:w="w"
 exclude-result-prefixes="ext w">
 <xsl:output omit-xml-declaration="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="w:p">
  <xsl:variable name="vrtfPass1">
   <p>
    <xsl:apply-templates/>
   </p>
  </xsl:variable>

  <xsl:apply-templates mode="pass2"
   select="ext:node-set($vrtfPass1)/*"/>
 </xsl:template>

 <xsl:template match="w:r">
  <xsl:variable name="vrtfProps">
   <xsl:for-each select="w:rPr/*">
    <xsl:sort select="local-name()"/>
    <xsl:copy-of select="."/>
   </xsl:for-each>
  </xsl:variable>

  <xsl:call-template name="toHtml">
   <xsl:with-param name="pProps" select=
       "ext:node-set($vrtfProps)/*"/>
   <xsl:with-param name="pText" select="w:t/text()"/>
  </xsl:call-template>
 </xsl:template>

 <xsl:template name="toHtml">
  <xsl:param name="pProps"/>
  <xsl:param name="pText"/>

  <xsl:choose>
   <xsl:when test="not($pProps)">
     <xsl:copy-of select="$pText"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:element name="{local-name($pProps[1])}">
      <xsl:call-template name="toHtml">
        <xsl:with-param name="pProps" select=
            "$pProps[position()>1]"/>
        <xsl:with-param name="pText" select="$pText"/>
      </xsl:call-template>
    </xsl:element>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

  <xsl:template match="/*" mode="pass2">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:call-template name="processInner">
     <xsl:with-param name="pNodes" select="node()"/>
    </xsl:call-template>
  </xsl:copy>
 </xsl:template>

 <xsl:template name="processInner">
  <xsl:param name="pNodes"/>

  <xsl:variable name="pNode1" select="$pNodes[1]"/>

  <xsl:if test="$pNode1">
   <xsl:choose>
    <xsl:when test="not($pNode1/self::*)">
     <xsl:copy-of select="$pNode1"/>
     <xsl:call-template name="processInner">
      <xsl:with-param name="pNodes" select=
      "$pNodes[position()>1]"/>
     </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="vbatchLength">
        <xsl:call-template name="getBatchLength">
         <xsl:with-param name="pNodes"
              select="$pNodes[position()>1]"/>
         <xsl:with-param name="pName"
             select="name($pNode1)"/>
         <xsl:with-param name="pCount" select="1"/>
        </xsl:call-template>
      </xsl:variable>

      <xsl:element name="{name($pNode1)}">
        <xsl:copy-of select="@*"/>

        <xsl:call-template name="processInner">
         <xsl:with-param name="pNodes" select=
         "$pNodes[not(position()>$vbatchLength)]
                        /node()"/>
        </xsl:call-template>
      </xsl:element>

      <xsl:call-template name="processInner">
       <xsl:with-param name="pNodes" select=
       "$pNodes[position()>$vbatchLength]"/>
      </xsl:call-template>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:if>
 </xsl:template>

 <xsl:template name="getBatchLength">
  <xsl:param name="pNodes"/>
  <xsl:param name="pName"/>
  <xsl:param name="pCount"/>

  <xsl:choose>
   <xsl:when test=
   "not($pNodes) or not($pNodes[1]/self::*)
    or not(name($pNodes[1])=$pName)">
   <xsl:value-of select="$pCount"/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:call-template name="getBatchLength">
     <xsl:with-param name="pNodes" select=
         "$pNodes[position()>1]"/>
     <xsl:with-param name="pName" select="$pName"/>
     <xsl:with-param name="pCount" select="$pCount+1"/>
    </xsl:call-template>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

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

<w:p xmlns:w="w">
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t xml:space="preserve">This is a </w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t xml:space="preserve">bold </w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
            <w:i/>
        </w:rPr>
        <w:t>with a bit of italic</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
            <w:i/>
        </w:rPr>
        <w:t> and some more italic</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:i/>
        </w:rPr>
        <w:t> and just italic, no-bold</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t xml:space="preserve"></w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t>paragr</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t>a</w:t>
    </w:r>
    <w:r>
        <w:rPr>
            <w:b/>
        </w:rPr>
        <w:t>ph</w:t>
    </w:r>
    <w:r>
        <w:t xml:space="preserve"> with some non-bold in it too.</w:t>
    </w:r>
</w:p>

желаемый, правильный результат получается :

<p><b>This is a bold <i>with a bit of italic and some more italic</i></b><i> and just italic, no-bold</i><b>paragraph</b> with some non-bold in it too.</p>

Объяснение

  1. Это двухпроходное преобразование . Первый этап относительно прост и преобразует исходный XML-документ (в нашем конкретном случае) в следующее:

результат pass1 (с отступом для удобства чтения):

<p>
   <b>This is a </b>
   <b>bold </b>
   <b>
      <i>with a bit of italic</i>
   </b>
   <b>
      <i> and some more italic</i>
   </b>
   <i> and just italic, no-bold</i>
   <b/>
   <b>paragr</b>
   <b>a</b>
   <b>ph</b> with some non-bold in it too.</p>

0,2. Второй проход (выполняется в режиме "pass2") объединяет любой пакет последовательных и одинаково именованных элементов в один элемент с таким именем. Он рекурсивно вызывает себя на дочерних элементах слитых элементов - таким образом, пакеты на любой глубине объединяются.

0,3. Обратите внимание : Мы не используем (и не можем) использовать оси following-sibling:: или preceding-sibling, потому что только узлы (подлежащие объединению) на верхнем уровне действительно являются родственными элементами. По этой причине мы обрабатываем все узлы как набор узлов.

0,4. Это решение является полностью общим - оно объединяет любую партию последовательных элементов с одинаковыми именами на любой глубине - и никакие конкретные имена не кодируются жестко.

3 голосов
/ 10 июня 2011

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

Обычно (очень обычно) содержимое документа WordML в основном состоит из w:p (параграфов) элементов, содержащих w:r run (область текста с такими же свойствами). Внутри каждого прогона обычно можно найти текстовые свойства региона (w:rPr) и сам текст (w:t).

Модель намного сложнее, но вы можете начать работать над этой общей структурой.

Например, вы можете начать работать со следующим (немного) общим преобразованием. Обратите внимание, что он обрабатывает только абзацы с жирным, курсивом и не выделенным текстом.


XSLT 2.0 протестировано под Saxon-HE 9.2.1.1J

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
    exclude-result-prefixes="w">
    <xsl:output method="html"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="w:document/w:body">
        <html>
            <body>
                <xsl:apply-templates select="w:p"/>
            </body>
        </html>
    </xsl:template>

    <!-- match paragraph -->
    <xsl:template match="w:p">
        <p>
            <xsl:apply-templates select="w:r"/>
        </p>
    </xsl:template>

    <!-- match run with property -->
    <xsl:template match="w:r[w:rPr]">
        <xsl:apply-templates select="w:rPr/*[1]"/>
    </xsl:template>

    <!-- Recursive template for bold, italic and underline
    properties applied to the same run. Escape to paragraph
    text -->
    <xsl:template match="w:b | w:i | w:u">
        <xsl:element name="{local-name(.)}">
            <xsl:choose>
                <!-- recurse to next sibling property i, b or u -->
                <xsl:when test="count(following-sibling::*[1])=1">
                    <xsl:apply-templates select="following-sibling::*
                        [local-name(.)='i' or 
                        local-name(.)='b' or 
                        local-name(.)='u']"/>
                </xsl:when>
                <xsl:otherwise>
                    <!-- escape to text -->
                    <xsl:apply-templates select="parent::w:rPr/
                        following-sibling::w:t"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:element>
    </xsl:template>

    <!-- match run without property -->
    <xsl:template match="w:r[not(w:rPr)]">
        <xsl:apply-templates select="w:t"/>
    </xsl:template>

    <!-- match text -->
    <xsl:template match="w:t">
        <xsl:value-of select="."/>
    </xsl:template>

</xsl:stylesheet>

Применяется:

<w:document xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">
    <w:body>
        <w:p>
            <w:r>
                <w:rPr>
                    <w:b/>
                </w:rPr>
                <w:t xml:space="preserve">This is a </w:t>
            </w:r>
            <w:r>
                <w:rPr>
                    <w:b/>
                </w:rPr>
                <w:t xml:space="preserve">bold </w:t>
            </w:r>
            <w:r>
                <w:rPr>
                    <w:b/>
                    <w:i/>
                </w:rPr>
                <w:t>with a bit of italic</w:t>
            </w:r>
            <w:r>
                <w:rPr>
                    <w:b/>
                </w:rPr>
                <w:t xml:space="preserve"> </w:t>
            </w:r>
            <w:r>
                <w:rPr>
                    <w:b/>
                </w:rPr>
                <w:t>paragr</w:t>
            </w:r>
            <w:r>
                <w:rPr>
                    <w:b/>
                </w:rPr>
                <w:t>a</w:t>
            </w:r>
            <w:r>
                <w:rPr>
                    <w:b/>
                </w:rPr>
                <w:t>ph</w:t>
            </w:r>
            <w:r>
                <w:t xml:space="preserve"> with some non-bold in it too.</w:t>
            </w:r>
        </w:p>
    </w:body>
</w:document>

производит:

<html>
   <body>
      <p><b>This is a </b><b>bold </b><b><i>with a bit of italic</i></b><b> </b><b>paragr</b><b>a</b><b>ph</b> with some non-bold in it too.
      </p>
   </body>
</html>

Побочный эффект наличия гротескного HTML-кода неизбежен из-за базовой схемы WordML. Возможно, задача сделать окончательный HTML более читабельным может быть отложена до некоторой удобной (и мощной) утилиты, такой как HTML tidy .

3 голосов
/ 10 июня 2011

Другой подход, похожий на тот, что был у Флинна, но с добавлением XSLT вместо добавления отдельного слоя для обработки текста, заключается в преобразовании исходного вывода HTML в той же таблице стилей, чтобы свести смежные элементы <b> или <i> в отдельные элементы..

Другими словами, таблица стилей сначала генерирует исходное дерево результатов HTML, а затем передает его в качестве входных данных в набор шаблонов (в специальном режиме), которые выполняли операцию свертывания.

Обновлено: Вот рабочая двухэтапная таблица стилей, построенная на основе таблицы стилей @ empo's stage-1:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
   xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs w"
   xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" version="2.0">

   <xsl:output method="html"/>
   <xsl:strip-space elements="*"/>
   <xsl:variable name="collapsibles" select="('i', 'b', 'u')"/>      

   <!-- identity template, except we collapse any adjacent b or i child elements. -->
   <xsl:template match="*" mode="collapse-adjacent">
      <xsl:copy>
         <xsl:copy-of select="@*"/>
         <xsl:for-each select="node()">
            <xsl:choose>
               <xsl:when test="index-of($collapsibles, local-name()) and
                     not(name(preceding-sibling::node()[1]) = name())">
                  <xsl:copy>
                     <xsl:copy-of select="@*"/>
                     <xsl:call-template name="process-niblings"/>
                  </xsl:copy>
               </xsl:when>
               <xsl:when test="index-of($collapsibles, local-name())"/>
               <!-- do not copy -->
               <xsl:otherwise>
                  <xsl:copy>
                     <xsl:copy-of select="@*"/>
                     <xsl:apply-templates mode="collapse-adjacent"/>
                  </xsl:copy>
               </xsl:otherwise>
            </xsl:choose>
         </xsl:for-each>
      </xsl:copy>
   </xsl:template>

   <!-- apply templates to children of current element *and* of all
      consecutively following elements of the same name. -->
   <xsl:template name="process-niblings">
      <xsl:apply-templates mode="collapse-adjacent"/>
      <!-- If immediate following sibling is the same element type, recurse with
         context node set to that sibling. -->
      <xsl:for-each
         select="following-sibling::node()[1][name() = name(current())]">
         <xsl:call-template name="process-niblings"/>
      </xsl:for-each>
   </xsl:template>

   <!-- @empo's stylesheet (modified) follows. --> 
   <xsl:template match="/">
      <html>
         <body>
            <xsl:variable name="raw-html">
               <xsl:apply-templates />
            </xsl:variable>
            <xsl:apply-templates select="$raw-html" mode="collapse-adjacent"/>            
         </body>
      </html>
   </xsl:template>

   <xsl:template match="w:document | w:body">
      <xsl:apply-templates />
   </xsl:template>

   <!-- match paragraph -->
   <xsl:template match="w:p">
      <p>
         <xsl:apply-templates select="w:r"/>
      </p>
   </xsl:template>

   <!-- match run with property -->
   <xsl:template match="w:r[w:rPr]">
      <xsl:apply-templates select="w:rPr/*[1]"/>
   </xsl:template>

   <!-- Recursive template for bold, italic and underline
      properties applied to the same run. Escape to paragraph
      text -->
   <xsl:template match="w:b | w:i | w:u">
      <xsl:element name="{local-name(.)}">
         <xsl:choose>
            <!-- recurse to next sibling property i, b or u -->
            <xsl:when test="count(following-sibling::*[1])=1">
               <xsl:apply-templates select="following-sibling::*
                  [index-of($collapsibles, local-name(.))]"/>
            </xsl:when>
            <xsl:otherwise>
               <!-- escape to text -->
               <xsl:apply-templates select="parent::w:rPr/
                  following-sibling::w:t"/>
            </xsl:otherwise>
         </xsl:choose>
      </xsl:element>
   </xsl:template>

   <!-- match run without property -->
   <xsl:template match="w:r[not(w:rPr)]">
      <xsl:apply-templates select="w:t"/>
   </xsl:template>

   <!-- match text -->
   <xsl:template match="w:t">
      <xsl:value-of select="."/>
   </xsl:template>

</xsl:stylesheet>

При повторном тестировании введенного вами примера ввода приведенная выше таблица стилей дает

<html>
   <body>
      <p><b>This is a bold <i>with a bit of italic</i> paragraph</b> with some non-bold in it too.
      </p>
   </body>
</html>

, который выглядит так, как вы хотели.

3 голосов
/ 10 июня 2011

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

<xsl:template match="w:p">
  <p>
    <xsl:apply-templates />
  </p>
</xsl:template>

<xsl:template match="w:r[w:rPr/w:b]">
  <b>
    <xsl:apply-templates />
  </b>
</xsl:template>

<xsl:template match="w:r[w:rPr/w:i]">
  <i>
    <xsl:apply-templates />
  </i>
</xsl:template>

<xsl:template match="w:r[w:rPr/w:i and w:rPr/w:b]">
  <b>
    <i>
      <xsl:apply-templates />
    </i>
  </b>
</xsl:template>

Будет выводить <p><b>This is a </b><b>bold </b><b><i>with a bit of italic</i></b><b> </b><b>paragr</b><b>a</b><b>ph</b> with some non-bold in it too.</p>

Затем вы можете использовать простую манипуляцию с текстом, чтобы удалить любые вхождения </b><b> и </i><i>, оставив вам:

<p><b>This is a bold <i>with a bit of italic</i> paragraph</b> with some non-bold in it too.</p>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...