1
1
mirror of https://github.com/wader/fq.git synced 2024-12-24 13:52:02 +03:00

Merge pull request #399 from wader/xml-more-ns

xml: Even more namespace fixes
This commit is contained in:
Mattias Wadman 2022-08-25 17:06:17 +02:00 committed by GitHub
commit 5d5f799bab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 179 additions and 78 deletions

View File

@ -1,4 +1,4 @@
$ fq . test.svg $ fq -r '. as $a | ., (toxml({indent: 2}) | ., (fromxml | ., (diff($a; .) // "no diff")))' test.svg
{ {
"svg": { "svg": {
"-height": "2500", "-height": "2500",
@ -22,7 +22,35 @@ $ fq . test.svg
} }
} }
} }
$ fq -o array=true . test.svg <svg height="2500" inkscape:version="1.0 (4035a4f, 2020-05-01)" sodipodi:docname="ffclippy.svg" version="1.1" viewBox="0 0 192.756 192.756" width="2500" xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
<g clip-rule="evenodd" fill-rule="evenodd" id="g863"></g>
<sodipodi:namedview pagecolor="#ffffff" showgrid="false"></sodipodi:namedview>
</svg>
{
"svg": {
"-height": "2500",
"-inkscape:version": "1.0 (4035a4f, 2020-05-01)",
"-sodipodi:docname": "ffclippy.svg",
"-version": "1.1",
"-viewBox": "0 0 192.756 192.756",
"-width": "2500",
"-xmlns": "http://www.w3.org/2000/svg",
"-xmlns:inkscape": "http://www.inkscape.org/namespaces/inkscape",
"-xmlns:sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
"-xmlns:svg": "http://www.w3.org/2000/svg",
"g": {
"-clip-rule": "evenodd",
"-fill-rule": "evenodd",
"-id": "g863"
},
"sodipodi:namedview": {
"-pagecolor": "#ffffff",
"-showgrid": "false"
}
}
}
no diff
$ fq -r -o array=true '. as $a | ., (toxml({indent: 2}) | ., (fromxml | ., (diff($a; .) // "no diff")))' test.svg
[ [
"svg", "svg",
{ {
@ -57,3 +85,42 @@ $ fq -o array=true . test.svg
] ]
] ]
] ]
<svg height="2500" inkscape:version="1.0 (4035a4f, 2020-05-01)" sodipodi:docname="ffclippy.svg" version="1.1" viewBox="0 0 192.756 192.756" width="2500" xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview pagecolor="#ffffff" showgrid="false"></sodipodi:namedview>
<g clip-rule="evenodd" fill-rule="evenodd" id="g863"></g>
</svg>
[
"svg",
{
"height": "2500",
"inkscape:version": "1.0 (4035a4f, 2020-05-01)",
"sodipodi:docname": "ffclippy.svg",
"version": "1.1",
"viewBox": "0 0 192.756 192.756",
"width": "2500",
"xmlns": "http://www.w3.org/2000/svg",
"xmlns:inkscape": "http://www.inkscape.org/namespaces/inkscape",
"xmlns:sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
"xmlns:svg": "http://www.w3.org/2000/svg"
},
[
[
"sodipodi:namedview",
{
"pagecolor": "#ffffff",
"showgrid": "false"
},
[]
],
[
"g",
{
"clip-rule": "evenodd",
"fill-rule": "evenodd",
"id": "g863"
},
[]
]
]
]
no diff

View File

@ -3,7 +3,8 @@ package xml
// object mode inspired by https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html // object mode inspired by https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html
// TODO: keep <?xml>? root #desc? // TODO: keep <?xml>? root #desc?
// TODO: xml default indent? // TODO: refactor to share more code
// TODO: rewrite ns stack
import ( import (
"bytes" "bytes"
@ -74,24 +75,26 @@ type xmlNS struct {
url string url string
} }
func elmName(space, local string) string { // TODO: nss pop? attr not stack?
if space == "" {
return local
}
return space + ":" + local
}
// xmlNNStack is used to undo namespace url resolving, space is url not the "alias" name // xmlNNStack is used to undo namespace url resolving, space is url not the "alias" name
type xmlNNStack []xmlNS type xmlNNStack []xmlNS
func (nss xmlNNStack) lookup(name xml.Name) string { func (nss xmlNNStack) lookup(name xml.Name) string {
var s string
for i := len(nss) - 1; i >= 0; i-- { for i := len(nss) - 1; i >= 0; i-- {
ns := nss[i] ns := nss[i]
if name.Space == ns.url { if name.Space == ns.url {
return ns.name // first found or is default namespace
if s == "" || ns.name == "" {
s = ns.name
}
if s == "" {
break
} }
} }
return "" }
return s
} }
func (nss xmlNNStack) push(name string, url string) xmlNNStack { func (nss xmlNNStack) push(name string, url string) xmlNNStack {
@ -100,57 +103,11 @@ func (nss xmlNNStack) push(name string, url string) xmlNNStack {
return xmlNNStack(n) return xmlNNStack(n)
} }
func fromXMLToArray(n xmlNode) any { func elmName(space, local string) string {
var f func(n xmlNode, nss xmlNNStack) []any if space == "" {
f = func(n xmlNode, nss xmlNNStack) []any { return local
attrs := map[string]any{}
for _, a := range n.Attrs {
local, space := a.Name.Local, a.Name.Space
name := local
if space != "" {
if space == "xmlns" {
nss = nss.push(local, a.Value)
} else {
space = nss.lookup(a.Name)
} }
name = space + ":" + local return space + ":" + local
} else if local == "xmlns" {
// track default namespace
nss = nss.push("", a.Value)
}
attrs[name] = a.Value
}
if attrs["#text"] == nil && !whitespaceRE.Match(n.Chardata) {
attrs["#text"] = strings.TrimSpace(string(n.Chardata))
}
if attrs["#comment"] == nil && !whitespaceRE.Match(n.Comment) {
attrs["#comment"] = strings.TrimSpace(string(n.Comment))
}
nodes := []any{}
for _, c := range n.Nodes {
nodes = append(nodes, f(c, nss))
}
local, space := n.XMLName.Local, n.XMLName.Space
if space != "" {
space = nss.lookup(n.XMLName)
}
name := elmName(space, local)
elm := []any{name}
if len(attrs) > 0 {
elm = append(elm, attrs)
} else {
// make attrs null if there were none, jq allows index into null
elm = append(elm, nil)
}
elm = append(elm, nodes)
return elm
}
return f(n, nil)
} }
func fromXMLToObject(n xmlNode, xi format.XMLIn) any { func fromXMLToObject(n xmlNode, xi format.XMLIn) any {
@ -160,19 +117,25 @@ func fromXMLToObject(n xmlNode, xi format.XMLIn) any {
for _, a := range n.Attrs { for _, a := range n.Attrs {
local, space := a.Name.Local, a.Name.Space local, space := a.Name.Local, a.Name.Space
name := local
if space != "" {
if space == "xmlns" { if space == "xmlns" {
nss = nss.push(local, a.Value) nss = nss.push(local, a.Value)
} else {
space = nss.lookup(a.Name)
}
name = space + ":" + local
} else if local == "xmlns" { } else if local == "xmlns" {
// track default namespace // track default namespace
nss = nss.push("", a.Value) nss = nss.push("", a.Value)
} }
}
for _, a := range n.Attrs {
local, space := a.Name.Local, a.Name.Space
name := local
if space != "" {
if space == "xmlns" {
// nop
} else {
space = nss.lookup(a.Name)
}
name = elmName(space, local)
}
attrs["-"+name] = a.Value attrs["-"+name] = a.Value
} }
@ -224,6 +187,64 @@ func fromXMLToObject(n xmlNode, xi format.XMLIn) any {
return map[string]any{name: attrs} return map[string]any{name: attrs}
} }
func fromXMLToArray(n xmlNode) any {
var f func(n xmlNode, nss xmlNNStack) []any
f = func(n xmlNode, nss xmlNNStack) []any {
attrs := map[string]any{}
for _, a := range n.Attrs {
local, space := a.Name.Local, a.Name.Space
if space == "xmlns" {
nss = nss.push(local, a.Value)
} else if local == "xmlns" {
// track default namespace
nss = nss.push("", a.Value)
}
}
for _, a := range n.Attrs {
local, space := a.Name.Local, a.Name.Space
name := local
if space != "" {
if space == "xmlns" {
// nop
} else {
space = nss.lookup(a.Name)
}
name = elmName(space, local)
}
attrs[name] = a.Value
}
if attrs["#text"] == nil && !whitespaceRE.Match(n.Chardata) {
attrs["#text"] = strings.TrimSpace(string(n.Chardata))
}
if attrs["#comment"] == nil && !whitespaceRE.Match(n.Comment) {
attrs["#comment"] = strings.TrimSpace(string(n.Comment))
}
nodes := []any{}
for _, c := range n.Nodes {
nodes = append(nodes, f(c, nss))
}
name := elmName(nss.lookup(n.XMLName), n.XMLName.Local)
elm := []any{name}
if len(attrs) > 0 {
elm = append(elm, attrs)
} else {
// make attrs null if there were none, jq allows index into null
elm = append(elm, nil)
}
elm = append(elm, nodes)
return elm
}
return f(n, nil)
}
func decodeXML(d *decode.D, in any) any { func decodeXML(d *decode.D, in any) any {
xi, _ := in.(format.XMLIn) xi, _ := in.(format.XMLIn)
@ -289,6 +310,20 @@ type ToXMLOpts struct {
Indent int Indent int
} }
func xmlNameFromStr(s string) xml.Name {
return xml.Name{Local: s}
}
func xmlNameSort(a, b xml.Name) bool {
if a.Space != b.Space {
if a.Space == "" {
return true
}
return a.Space < b.Space
}
return a.Local < b.Local
}
func toXMLFromObject(c any, opts ToXMLOpts) any { func toXMLFromObject(c any, opts ToXMLOpts) any {
var f func(name string, content any) (xmlNode, int, bool) var f func(name string, content any) (xmlNode, int, bool)
f = func(name string, content any) (xmlNode, int, bool) { f = func(name string, content any) (xmlNode, int, bool) {
@ -319,10 +354,11 @@ func toXMLFromObject(c any, opts ToXMLOpts) any {
n.Comment = []byte(s) n.Comment = []byte(s)
case strings.HasPrefix(k, "-"): case strings.HasPrefix(k, "-"):
s, _ := v.(string) s, _ := v.(string)
n.Attrs = append(n.Attrs, xml.Attr{ a := xml.Attr{
Name: xml.Name{Local: k[1:]}, Name: xmlNameFromStr(k[1:]),
Value: s, Value: s,
}) }
n.Attrs = append(n.Attrs, a)
default: default:
switch v := v.(type) { switch v := v.(type) {
case []any: case []any:
@ -360,8 +396,7 @@ func toXMLFromObject(c any, opts ToXMLOpts) any {
} }
sort.Slice(n.Attrs, func(i, j int) bool { sort.Slice(n.Attrs, func(i, j int) bool {
a, b := n.Attrs[i].Name, n.Attrs[j].Name return xmlNameSort(n.Attrs[i].Name, n.Attrs[j].Name)
return a.Space < b.Space || a.Local < b.Local
}) })
return n, seq, hasSeq return n, seq, hasSeq
@ -415,7 +450,7 @@ func toXMLFromArray(c any, opts ToXMLOpts) any {
} }
n := xmlNode{ n := xmlNode{
XMLName: xml.Name{Local: name}, XMLName: xmlNameFromStr(name),
} }
for k, v := range attrs { for k, v := range attrs {
@ -429,15 +464,14 @@ func toXMLFromArray(c any, opts ToXMLOpts) any {
default: default:
s, _ := v.(string) s, _ := v.(string)
n.Attrs = append(n.Attrs, xml.Attr{ n.Attrs = append(n.Attrs, xml.Attr{
Name: xml.Name{Local: k}, Name: xmlNameFromStr(k),
Value: s, Value: s,
}) })
} }
} }
sort.Slice(n.Attrs, func(i, j int) bool { sort.Slice(n.Attrs, func(i, j int) bool {
a, b := n.Attrs[i].Name, n.Attrs[j].Name return xmlNameSort(n.Attrs[i].Name, n.Attrs[j].Name)
return a.Space < b.Space || a.Local < b.Local
}) })
for _, c := range children { for _, c := range children {