我有一些JSON,一个XPath和一个值。我想用新值替换XPath指向的JSON属性中的现有值。我当时以为我可以使用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" ]
}
}
相反,我得到的是原件的副本。我尝试了一些变体,但距离还不很近。我应该期望这行得通吗?
答案 0 :(得分:3)
问题似乎是MarkLogic的XSLT处理器对JSON扩展的处理程度与其XQuery处理器不同。 <xsl:copy>
似乎被object-node()
短路了,它不仅复制了上下文节点,而且还像<xsl:copy-of>
一样,复制了所有后代,从而阻止了LowProperty1
模板(以及任何其他模板)执行。您可以通过在<xsl:message>
模板中添加LowProperty1
来确认这一点,并查看该消息从未记录。
据我所知,没有从XSLT内部复制JSON节点的惯用方法。因此,另一种方法是在转换之前和之后简单地与json:object
进行转换-当然,这可以在运行XSLT之前在XQuery中完成(可能更可取)。
<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>
答案 1 :(得分:3)
如果设置了xdmp:dialect="1.0-ml"
,则可以对JSON节点类型使用模板匹配模式:object-node()
,array-node()
,number-node()
,boolean-node()
,{ {1}}不仅可以使用XPath并根据节点名称(例如null-node()
)匹配模式。
不幸的是,SomeProperty/LowProperty1
执行的深层复制使其难以转换,并且这些JSON节点没有XSLT节点构造函数。
因此,将JSON转换为XML,HTML和文本非常简单,但是为了构造所需的转换后的JSON,您将希望像@wst所示那样与xsl:copy
进行转换,或者您可以作弊一点,只生成JSON文本。
使用一些与JSON节点匹配的基本模板并生成JSON文本输出,然后可以添加自己的专用模板以更改json:object
值:
SomeProperty/LowProperty1
答案 2 :(得分:2)
除非MarkLogic做了我不知道的扩展标准XSLT语义的操作,否则这将行不通。 SomeProperty/LowProperty1
之类的匹配模式不能用于解决地图/数组树的各个部分。您可以在这样的树中匹配事物,但是它并不是很有用,因为匹配不是上下文相关的:给定一个映射或数组,您将无法找到它的位置或到达那里的方式。
您可能会发现阅读有关使用XSLT 3.0转换JSON的XML Prague 2016论文很有用:http://www.saxonica.com/papers/xmlprague-2016mhk.pdf
使用XSLT模板匹配来转换XML的标准方法不能很好地转换为JSON,其根本原因是用于表示JSON的地图/数组结构没有“节点标识”或向上导航(父指针) 。在我的论文示例中,我通常发现进行这种转换的最简单方法是将结构转换为XML,转换XML,然后再转换回去,尽管您可能会考虑其他方法。
我一直试图为高阶扩展功能设计一个使这种任务更容易的设计。我认为我还没有理想的解决方案。
答案 3 :(得分:2)
@MadsHansen的xdmp:dialect="1.0-ml"
选项的发现启发了我,我创建了另一个答案的更惯用的版本。使用此XSLT,您可以使用MarkLogic JSON XPath扩展(即match="SomeProperty/LowProperty1"
)来创建模板。
此处的区别在于,本机JSON对象最初保持不变,而不是在一开始就转换为json:object
XML批发,并且在转换期间仅转换为json:object
。然后最后,一切都将转换回本机。唯一的缺点是,您需要在模板内部构造新的JSON时使用json:object
XML,或将本机构造函数包装在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求值之前,本机语法在XQuery中进行评估并转换为json:object
XML。
答案 4 :(得分:1)
我同意@wst认为xsl:copy
有点奇怪。实际上,至少在ML9中,它确实是在复制而不是递归到对象节点。尚未使用ML10进行测试。
@ mads-hansen提到的xdmp:dialect
在这里绝对有用,尽管复制时不必将整个树转换为XML表示法,最后也不必转换为JSON。您还可以使用内部json:object类型,并在需要时将其转换为浅层。
这显示了如何用适用于JSON的内容替换常用的身份转换:
xquery version "1.0-ml";
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"
xmlns:json="http://marklogic.com/xdmp/json">
<xsl:template match="object-node()">
<xsl:variable name="this" select="json:object()"/>
<xsl:for-each select="node()">
<xsl:variable name="contents" as="item()*">
<xsl:apply-templates select="."/>
</xsl:variable>
<xsl:sequence select="map:put($this, name(.), $contents)"/>
</xsl:for-each>
<xsl:sequence select="xdmp:to-json($this)"/>
</xsl:template>
<xsl:template match="array-node()">
<xsl:variable name="contents" as="item()*">
<xsl:apply-templates select="node()"/>
</xsl:variable>
<xsl:sequence select="json:to-array($contents)"/>
</xsl:template>
<xsl:template match="text()">
<xsl:sequence select="."/>
</xsl:template>
<xsl:template match="SomeProperty/LowProperty1">
<xsl:text>foo</xsl:text>
</xsl:template>
</xsl:stylesheet>,
$stuff
)
HTH!