Как изменить одно свойство JSON с помощью MarkLogic XSLT? - PullRequest
0 голосов
/ 06 сентября 2018

У меня есть JSON, XPath и значение. Я хочу заменить существующее значение в свойстве JSON, на которое указывает XPath, новым значением. Я думал, что смогу сделать это с XSLT, но я не очень хорош в XSLT. Это будет в модуле XQuery.

Для XML я могу сделать что-то вроде этого:

let $content :=
  document {
    <class> 
      <student rollno = "393"> 
        <firstname>Dinkar</firstname> 
        <lastname>Kad</lastname> 
        <nickname>Dinkar</nickname> 
        <marks>85</marks> 
      </student> 
      <student rollno = "493"> 
        <firstname>Vaneet</firstname> 
        <lastname>Gupta</lastname> 
        <nickname>Vinni</nickname> 
        <marks>95</marks> 
      </student>
    </class>
  }
let $template := 
  <xsl:stylesheet version = "1.0" 
    xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
    <xsl:template match = "node()|@*"> 
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>
    <xsl:template match="student/marks">
      <foo>bar</foo>
    </xsl:template>
  </xsl:stylesheet>
return
    xdmp:xslt-eval($template, $content)

Правильно заменяет элементы class/student/marks элементом <foo>bar</foo>.

Для JSON, я пытаюсь это:

let $stuff :=
  document {
    object-node {
      "SomeProperty": object-node {
        "LowProperty1":"some string", 
        "LowProperty2":"some string", 
        "LowProperty3": array-node { "some string 1", "some string 2"}
      }
    }
  }

let $target := xdmp:unpath("/EvenLowerProperty/LowestProperty1", (), $stuff)
return
  xdmp:xslt-eval(
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="2.0"
        xmlns:json="http://marklogic.com/xdmp/json">
      <xsl:template match="node()"> 
        <xsl:copy>
          <xsl:apply-templates select="node()"/>
        </xsl:copy>
      </xsl:template>
      <xsl:template match="SomeProperty/LowProperty1">
        {
          map:entry("LowProperty1", "bar")
        }
      </xsl:template>
    </xsl:stylesheet>,
    $stuff
  )

Я хочу закончить с этим:

{
  "SomeProperty": {
    "LowProperty1":"bar", 
    "LowProperty2":"some string", 
    "LowProperty3": [ "some string 1", "some string 2" ]
  }
}

Вместо этого я получаю копию оригинала. Я попробовал некоторые варианты, но я не становлюсь ближе. Должен ли я ожидать, что это сработает?

Ответы [ 4 ]

0 голосов
/ 07 сентября 2018

Я был вдохновлен открытием @ MadsHansen опции xdmp:dialect="1.0-ml", чтобы создать более идиоматическую версию моего другого ответа. Используя этот XSLT, вы сохраняете возможность создавать шаблоны с использованием расширений MarkLogic JSON XPath (т. Е. match="SomeProperty/LowProperty1").

Разница здесь в том, что вместо преобразования в json:object XML оптом в начале нативные объекты JSON поддерживаются изначально и только преобразуются в json:object во время преобразования. Затем в конце все превращается обратно в родной. Единственным недостатком является то, что вам нужно либо использовать json:object XML при создании новых JSON внутри шаблонов, либо обернуть собственные конструкторы в xdmp:from-json():

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0"
    xmlns:json="http://marklogic.com/xdmp/json"
    xmlns:xdmp="http://marklogic.com/xdmp"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xdmp:dialect="1.0-ml">

    <!-- XML -->
    <xsl:template match="SomeProperty/LowProperty1">
      <xsl:text>bar</xsl:text>
    </xsl:template>     

    <!-- Native JSON syntax -->
    <xsl:template match="SomeProperty/LowProperty2">
      {xdmp:from-json(
        object-node { "foo" : "bar" }
      )}
    </xsl:template>  

    <!-- Conversion handling -->

    <xsl:template match="/">
      <xsl:variable name="result" as="node()">
        <xsl:apply-templates select="@*|node()"/>
      </xsl:variable>
      <xsl:choose>
        <xsl:when test="namespace-uri-from-QName($result/node-name(.)) = 'http://marklogic.com/xdmp/json'">
          <xsl:sequence select="xdmp:to-json(json:object($result))"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:sequence select="$result"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>

    <!-- Identity templates below -->

    <xsl:template name="json:value">
      <xsl:variable name="result" as="node()">
        <xsl:apply-templates select="."/>
      </xsl:variable>        
      <json:value>                       
        <xsl:if test=". instance of number-node()">
          <xsl:attribute name="xsi:type">
            <xsl:value-of select="xs:QName('xs:integer')"/>
          </xsl:attribute>
        </xsl:if>
        <xsl:sequence select="$result"/>
      </json:value>
    </xsl:template>

    <xsl:template match="object-node()"> 
      <json:object>
        <xsl:for-each select="node()">
          <json:entry key="{{ name(.) }}">
            <xsl:call-template name="json:value"/>
          </json:entry>
        </xsl:for-each>
      </json:object>
    </xsl:template> 

    <xsl:template match="array-node()"> 
      <json:array>        
        <xsl:for-each select="node()">
          <xsl:call-template name="json:value"/>
        </xsl:for-each>
      </json:array>
    </xsl:template> 

    <xsl:template match="number-node()">
      <xsl:value-of select="."/>
    </xsl:template>

    <xsl:template match="node()|@*" priority="-1">
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Также обратите внимание, что собственный синтаксис JSON работает только при использовании с xdmp:xslt-eval - собственный синтаксис оценивается в XQuery и преобразуется в json:object XML перед оценкой XSLT.

0 голосов
/ 06 сентября 2018

Проблема заключается в том, что XSLT-процессор MarkLogic не обрабатывает расширения JSON в той же степени, что и его XQuery-процессор.<xsl:copy> кажется коротким замыканием object-node() и вместо копирования только узла контекста ведет себя как <xsl:copy-of>, копируя всех потомков, что препятствует выполнению шаблона LowProperty1 (и любого другого шаблона).Вы можете подтвердить это, добавив <xsl:message> к шаблону LowProperty1 и увидев, что сообщение никогда не регистрируется.

Насколько я могу судить, не существует идиоматического способа копирования узлов JSON из XSLT,Поэтому альтернативой является простое преобразование в / из json:object до и после преобразования - и, конечно, это можно сделать в XQuery (что может быть предпочтительнее) перед запуском XSLT.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0"
    xmlns:json="http://marklogic.com/xdmp/json"
    xmlns:xdmp="http://marklogic.com/xdmp">

   <xsl:template match="document-node()">
     <xsl:variable name="jsonxml" as="element()">
       <temp><xsl:sequence select="xdmp:from-json(.)"/></temp>
     </xsl:variable>
     <xsl:variable name="result" as="element(json:object)">
       <xsl:apply-templates select="$jsonxml/json:object"/>
     </xsl:variable>
     <xsl:sequence select="xdmp:to-json(json:object($result))"/>   
   </xsl:template>

    <xsl:template match="node()|@*"> 
      <xsl:copy>
        <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>

    <xsl:template match="json:entry[@key='LowProperty1']/json:value">
      <xsl:copy>
        <xsl:text>bar</xsl:text>
      </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
0 голосов
/ 07 сентября 2018

Если вы установите xdmp:dialect="1.0-ml", то вы можете использовать шаблоны соответствия шаблонов для типов узлов JSON: object-node(), array-node(), number-node(), boolean-node(), null-node() в дополнение возможность использовать XPath и сопоставлять шаблоны на основе имен узлов, таких как SomeProperty/LowProperty1.

К сожалению, xsl:copy выполняет глубокое копирование, что затрудняет преобразование, и для этих узлов JSON нет доступных конструкторов узлов XSLT.

Таким образом, преобразовать JSON в XML, HTML и текст довольно легко, но для того, чтобы создать желаемый преобразованный JSON, вам нужно либо преобразовать в json:object, как продемонстрировано @wst, либо вы могли бы немного обмануть и просто сгенерировать текст в формате JSON.

Используя некоторые базовые шаблоны, которые соответствуют узлам JSON и генерируют его вывод текста JSON, вы можете добавить свой собственный специализированный шаблон для изменения значения SomeProperty/LowProperty1:

let $stuff :=
  document {
    object-node {
      "SomeProperty": object-node {
        "LowProperty1":"some string", 
        "LowProperty2":"some string", 
        "LowProperty3": array-node { "some string 1", "some string 2"}
      }
    }
  }

let $target := xdmp:unpath("/EvenLowerProperty/LowestProperty1", (), $stuff)
return
  xdmp:xslt-eval(
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
        xdmp:dialect="1.0-ml">

      <xsl:output method="text"/>

      <xsl:variable name="lcurly" select="'&#123;'"/>
      <xsl:variable name="rcurly" select="'&#125;'"/>

      <xsl:template match="node()">
      <xsl:apply-templates select="." mode="name"/>
        <xsl:apply-templates select="." mode="value"/>
      </xsl:template>

      <xsl:template match="array-node()/node()">
        <xsl:apply-templates select="." mode="value"/>
      </xsl:template>

      <xsl:template match="node()" mode="name">
        <xsl:if test="string(node-name(.))!=''">"<xsl:value-of select="node-name(.)"/>": </xsl:if>
      </xsl:template>

      <xsl:template match="text()" mode="value">
        <xsl:text>"</xsl:text><xsl:value-of select="."/><xsl:text>"</xsl:text>
        <xsl:if test="following-sibling::node()">, </xsl:if>
      </xsl:template>

      <xsl:template match="number-node() | boolean-node()" mode="value">
        <xsl:value-of select="."/>
        <xsl:if test="following-sibling::node()">, </xsl:if>
      </xsl:template>

      <xsl:template match="object-node()" mode="value">
        <xsl:value-of select="$lcurly"/>
        <xsl:apply-templates select="node()"/>
        <xsl:value-of select="$rcurly"/> 
         <xsl:if test="following-sibling::node()">,</xsl:if>
      </xsl:template>

      <xsl:template match="array-node()/object-node()" mode="value">
        <xsl:value-of select="$lcurly"/>
        <xsl:apply-templates select="node()"/>
        <xsl:value-of select="$rcurly"/>
        <xsl:if test="following-sibling::node()">,</xsl:if>
      </xsl:template>

      <xsl:template match="array-node()" mode="value">
        <xsl:value-of select="'['"/>
        <xsl:apply-templates select="node()"/>
        <xsl:value-of select="']'"/> 
        <xsl:if test="following-sibling::node()">,</xsl:if>
      </xsl:template>

      <xsl:template match="null-node()" mode="value">
        <xsl:value-of select="'null'"/>
        <xsl:if test="following-sibling::node()">, </xsl:if>
      </xsl:template>

      <xsl:template match="SomeProperty/LowProperty1">
        <xsl:apply-templates select="." mode="name"/>
        <xsl:text>"bar"</xsl:text>
        <xsl:if test="following-sibling::node()">, </xsl:if>
      </xsl:template>

    </xsl:stylesheet>,
    $stuff
  )
0 голосов
/ 06 сентября 2018

Если MarkLogic не сделал что-то, о чем я не знаю, чтобы расширить стандартную семантику XSLT, это не сработает. Шаблоны соответствия, такие как SomeProperty/LowProperty1, не могут использоваться для адресации частей дерева карты / массива. Вы можете сопоставлять вещи в таком дереве, но это не очень полезно, потому что совпадение не может быть контекстно-зависимым: учитывая карту или массив, вы не можете узнать, где оно находится и как вы туда попали.

Возможно, вам будет полезно прочитать мою статью XML Prague 2016 о преобразовании JSON с использованием XSLT 3.0: http://www.saxonica.com/papers/xmlprague-2016mhk.pdf

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

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

...