github.com/boki/go-xmp@v1.0.1/xmp/json.go (about)

     1  // Copyright (c) 2017-2018 Alexander Eichhorn
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"): you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    11  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    12  // License for the specific language governing permissions and limitations
    13  // under the License.
    14  
    15  package xmp
    16  
    17  import (
    18  	"encoding/json"
    19  	"encoding/xml"
    20  	"fmt"
    21  	"reflect"
    22  	"strconv"
    23  )
    24  
    25  type jsonDocument struct {
    26  	About      string                     `json:"about,omitempty"`
    27  	Toolkit    string                     `json:"toolkit,omitempty"`
    28  	Namespaces map[string]string          `json:"namespaces"`
    29  	Models     map[string]json.RawMessage `json:"models"`
    30  }
    31  
    32  type jsonOutDocument struct {
    33  	About      string                 `json:"about,omitempty"`
    34  	Toolkit    string                 `json:"toolkit,omitempty"`
    35  	Namespaces map[string]string      `json:"namespaces"`
    36  	Models     map[string]interface{} `json:"models"`
    37  }
    38  
    39  func (d *Document) MarshalJSON() ([]byte, error) {
    40  	// sync individual models to establish correct XMP entries
    41  	if err := d.syncToXMP(); err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	out := &jsonOutDocument{
    46  		About:      d.about,
    47  		Toolkit:    d.toolkit,
    48  		Namespaces: make(map[string]string),
    49  		Models:     make(map[string]interface{}),
    50  	}
    51  
    52  	if out.Toolkit == "" {
    53  		out.Toolkit = XMP_TOOLKIT_VERSION
    54  	}
    55  
    56  	// We're using the regular XMP decoder with a JSON boilerplate.
    57  	e := NewEncoder(nil)
    58  	e.intNsMap = d.intNsMap
    59  	e.extNsMap = d.extNsMap
    60  	defer e.root.Close()
    61  
    62  	// 1  build output node tree (model -> nodes+attr with one root node per
    63  	//    XMP namespace)
    64  	for _, n := range d.nodes {
    65  		// 1.1  encode the model (Note: models typically use multiple XMP namespaces)
    66  		//      so we generate wrapper nodes on the fly
    67  		if n.Model != nil {
    68  			if err := e.marshalValue(reflect.ValueOf(n.Model), nil, e.root, true); err != nil {
    69  				return nil, err
    70  			}
    71  		}
    72  
    73  		// 1.2  merge external nodes (Note: all ext nodes collected under a
    74  		//      document node belong to the same namespace)
    75  		ns := e.findNs(n.XMLName)
    76  		if ns == nil {
    77  			return nil, fmt.Errorf("xmp: missing namespace for model node %s\n", n.XMLName.Local)
    78  		}
    79  		node := e.root.Nodes.FindNode(ns)
    80  		if node == nil {
    81  			node = NewNode(n.XMLName)
    82  			e.root.AddNode(node)
    83  		}
    84  		node.Nodes = append(node.Nodes, copyNodes(n.Nodes)...)
    85  
    86  		// 1.3  merge external attributes (Note: all ext attr collected under a
    87  		//      document node belong to the same namespace)
    88  		node.Attr = append(node.Attr, n.Attr...)
    89  	}
    90  
    91  	// 2  collect root-node namespaces
    92  	for _, n := range e.root.Nodes {
    93  		for _, v := range n.Namespaces(d) {
    94  			if v == nsX || v == nsXML || v == nsRDF {
    95  				continue
    96  			}
    97  			out.Namespaces[v.GetName()] = v.GetURI()
    98  		}
    99  	}
   100  
   101  	// 3 convert node tree to json, ignore empty root nodes
   102  	for _, n := range e.root.Nodes {
   103  		if n.IsZero() {
   104  			continue
   105  		}
   106  		if m, err := nodeToJson(n); err != nil {
   107  			return nil, err
   108  		} else {
   109  			// add model under it's namespace name
   110  			out.Models[n.Namespace()] = m
   111  		}
   112  	}
   113  
   114  	b, err := json.Marshal(out)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	// 4 output as json
   120  	return b, nil
   121  }
   122  
   123  func nodeToJson(n *Node) (interface{}, error) {
   124  	// process value leaf nodes (Note: value and model are mutually exclusive)
   125  	if n.Value != "" {
   126  		return &n.Value, nil
   127  	}
   128  
   129  	out := make(map[string]interface{})
   130  
   131  	// add node attributes
   132  	for _, v := range n.Attr {
   133  		name := v.Name.Local
   134  		// Note:  stripping rdf attributes from external child nodes
   135  		//        that are relinked during marshal with all attributes
   136  		if name == "rdf:parseType" {
   137  			continue
   138  		}
   139  		out[name] = v.Value
   140  	}
   141  
   142  	// add node children
   143  	for _, v := range n.Nodes {
   144  		switch v.FullName() {
   145  		case "rdf:Bag", "rdf:Seq":
   146  			// skip outer node and unwrap inner li nodes as array elements
   147  			a := make([]interface{}, 0)
   148  			for _, vv := range v.Nodes {
   149  				if vv.Value != "" {
   150  					// simple lists (i.e. string lists or numbers etc)
   151  					a = append(a, &vv.Value)
   152  				} else {
   153  					// object lists
   154  					// m := make(map[string]interface{})
   155  					if m, err := nodeToJson(vv); err != nil {
   156  						return nil, err
   157  					} else {
   158  						a = append(a, m)
   159  					}
   160  				}
   161  			}
   162  			return a, nil
   163  
   164  		case "rdf:Alt":
   165  			// determine content type (strings or objects)
   166  			if len(v.Nodes) > 0 && len(v.Nodes[0].Nodes) > 0 {
   167  				// object type: skip outer node and insert interface
   168  				a := make([]interface{}, 0)
   169  				for _, vv := range v.Nodes {
   170  					if m, err := nodeToJson(vv); err != nil {
   171  						return nil, err
   172  					} else {
   173  						a = append(a, m)
   174  					}
   175  				}
   176  				return a, nil
   177  			} else {
   178  				// string type: skip outer node and insert ArrayItems
   179  				var defLangContent string
   180  				var defLangIndex int = -1
   181  				a := make([]*AltItem, 0)
   182  				for i, vv := range v.Nodes {
   183  					// string arrays
   184  					ai := &AltItem{
   185  						Value: vv.Value,
   186  					}
   187  					langAttr := vv.GetAttr("", "lang")
   188  					if len(langAttr) > 0 {
   189  						ai.Lang = langAttr[0].Value
   190  					}
   191  					if ai.Lang == "x-default" {
   192  						ai.IsDefault = true
   193  						defLangContent = ai.Value
   194  						defLangIndex = i
   195  					}
   196  					a = append(a, ai)
   197  				}
   198  				// mark the default item
   199  				if defLangContent != "" && len(a) > 1 {
   200  					for i, l := 0, len(a); i < l; i++ {
   201  						if a[i].Value == defLangContent {
   202  							a[i].IsDefault = true
   203  						}
   204  					}
   205  					// remove the duplicate default element
   206  					if defLangIndex > -1 {
   207  						a = append(a[:defLangIndex], a[defLangIndex+1:]...)
   208  					}
   209  				}
   210  				return a, nil
   211  			}
   212  
   213  		default:
   214  			if m, err := nodeToJson(v); err != nil {
   215  				return nil, err
   216  			} else {
   217  				out[v.FullName()] = m
   218  			}
   219  		}
   220  	}
   221  
   222  	return out, nil
   223  }
   224  
   225  // The difference to regular JSON decoding is that we must take care of
   226  // unknown properties that Go's JSON decoder simply ignores. In our XMP
   227  // model we capture everything unknown in child nodes. Note: due to internal
   228  // namespace lookups we cannot use node attributes, because attribute unmarshal
   229  // will fail for all unknown namespaces.
   230  func (d *Document) UnmarshalJSON(data []byte) error {
   231  	in := &jsonDocument{
   232  		Namespaces: make(map[string]string),
   233  		Models:     make(map[string]json.RawMessage),
   234  	}
   235  
   236  	if err := json.Unmarshal(data, in); err != nil {
   237  		return fmt.Errorf("xmp: json unmarshal failed: %v", err)
   238  	}
   239  
   240  	// We're using the regular XMP decoder with a JSON boilerplate.
   241  	dec := NewDecoder(nil)
   242  
   243  	// register namespaces
   244  	dec.about = in.About
   245  	dec.toolkit = in.Toolkit
   246  	for prefix, uri := range in.Namespaces {
   247  		dec.addNamespace(prefix, uri)
   248  	}
   249  
   250  	// build node tree from JSON models
   251  	root := NewNode(emptyName)
   252  	defer root.Close()
   253  	for name, b := range in.Models {
   254  		node := NewNode(xml.Name{Local: name})
   255  		root.Nodes = append(root.Nodes, node)
   256  		content := make(map[string]interface{})
   257  		if err := json.Unmarshal(b, &content); err != nil {
   258  			return fmt.Errorf("xmp: json unmarshal model '%s' failed: %v", name, err)
   259  		}
   260  		for n, v := range content {
   261  			jsonToNode(n, v, node)
   262  		}
   263  	}
   264  
   265  	// run node tree through xmp unmarshaler
   266  	for _, n := range root.Nodes {
   267  		// process attributes
   268  		for _, v := range n.Attr {
   269  			if err := dec.decodeAttribute(&dec.nodes, v); err != nil {
   270  				return err
   271  			}
   272  		}
   273  		// process child nodes
   274  		for _, v := range n.Nodes {
   275  			if err := dec.decodeNode(&dec.nodes, v); err != nil {
   276  				return err
   277  			}
   278  		}
   279  	}
   280  
   281  	// copy decoded values to document
   282  	d.toolkit = dec.toolkit
   283  	d.about = dec.about
   284  	d.nodes = dec.nodes
   285  	d.intNsMap = dec.intNsMap
   286  	d.extNsMap = dec.extNsMap
   287  	return d.syncFromXMP()
   288  }
   289  
   290  func jsonToNode(name string, v interface{}, node *Node) {
   291  	switch {
   292  	case isSimpleValue(v):
   293  		var s string
   294  		switch val := v.(type) {
   295  		case string:
   296  			s = val
   297  		case float64:
   298  			s = strconv.FormatFloat(val, 'f', -1, 64)
   299  		case bool:
   300  			s = strconv.FormatBool(val)
   301  		case nil:
   302  			return
   303  		}
   304  		if name != "" && name != "rdf:value" {
   305  			// add simple values as child nodes with string value
   306  			attrNode := NewNode(xml.Name{Local: name})
   307  			attrNode.Value = s
   308  			node.Nodes = append(node.Nodes, attrNode)
   309  		} else {
   310  			// add as node value when no name is given (i.e. used in string arrays)
   311  			node.Value = s
   312  		}
   313  
   314  	case isArrayValue(v):
   315  		// arrays of arrays are not supported in XMP
   316  		if name == "" {
   317  			return
   318  		}
   319  
   320  		// add arrays as Seq/li or Alt/li child nodes
   321  		containerNode := NewNode(xml.Name{Local: name})
   322  		node.Nodes = append(node.Nodes, containerNode)
   323  		typ := getArrayType(v)
   324  		anode := NewNode(xml.Name{Space: nsRDF.GetURI(), Local: string(typ)})
   325  		containerNode.Nodes = append(containerNode.Nodes, anode)
   326  
   327  		for _, av := range v.([]interface{}) {
   328  			linode := NewNode(xml.Name{Space: nsRDF.GetURI(), Local: "li"})
   329  			anode.Nodes = append(anode.Nodes, linode)
   330  			if typ == ArrayTypeAlternative {
   331  				item := av.(map[string]interface{})
   332  				def := item["isDefault"].(bool)
   333  				lang := item["lang"].(string)
   334  				val := item["value"].(string)
   335  
   336  				// add single node when default || !default && lang != ""
   337  				if def || !def && lang != "" {
   338  					if def {
   339  						linode.Attr = append(linode.Attr, Attr{
   340  							Name:  xml.Name{Local: "xml:lang"},
   341  							Value: "x-default",
   342  						})
   343  						linode.Value = val
   344  					} else {
   345  						linode.Attr = append(linode.Attr, Attr{
   346  							Name:  xml.Name{Local: "xml:lang"},
   347  							Value: lang,
   348  						})
   349  						linode.Value = val
   350  					}
   351  				}
   352  
   353  				// add second node for default && lang != ""
   354  				if def && lang != "" {
   355  					linode = NewNode(xml.Name{Space: nsRDF.GetURI(), Local: "li"})
   356  					anode.Nodes = append(anode.Nodes, linode)
   357  					linode.Attr = append(linode.Attr, Attr{
   358  						Name:  xml.Name{Local: "xml:lang"},
   359  						Value: lang,
   360  					})
   361  					linode.Value = val
   362  				}
   363  			} else {
   364  				jsonToNode("", av, linode)
   365  			}
   366  		}
   367  
   368  	case isObjectValue(v):
   369  		// add objects as child nodes (recursive)
   370  		onode := node
   371  		if name != "" {
   372  			onode = NewNode(xml.Name{Local: name})
   373  			node.Nodes = append(node.Nodes, onode)
   374  		}
   375  		for on, ov := range v.(map[string]interface{}) {
   376  			if isArrayItemType(v) && on == "value" {
   377  				jsonToNode("", ov, onode)
   378  			} else {
   379  				jsonToNode(on, ov, onode)
   380  			}
   381  		}
   382  	}
   383  }
   384  
   385  // JSON unmarshal helpers
   386  func isSimpleValue(v interface{}) bool {
   387  	switch v.(type) {
   388  	case string, float64, bool, nil:
   389  		return true
   390  	default:
   391  		return false
   392  	}
   393  }
   394  
   395  func isObjectValue(v interface{}) bool {
   396  	switch v.(type) {
   397  	case map[string]interface{}:
   398  		return true
   399  	default:
   400  		return false
   401  	}
   402  }
   403  
   404  func isArrayValue(v interface{}) bool {
   405  	switch v.(type) {
   406  	case []interface{}:
   407  		return true
   408  	default:
   409  		return false
   410  	}
   411  }
   412  
   413  func isArrayItemType(v interface{}) bool {
   414  	// alternative array item
   415  	if isObjectValue(v) {
   416  		val := v.(map[string]interface{})
   417  		if len(val) > 3 {
   418  			return false
   419  		}
   420  		if _, ok := val["value"]; !ok {
   421  			return false
   422  		}
   423  		if _, ok := val["lang"]; !ok {
   424  			return false
   425  		}
   426  		if _, ok := val["isDefault"]; !ok {
   427  			return false
   428  		}
   429  		return true
   430  	}
   431  	return false
   432  }
   433  
   434  func getArrayType(v interface{}) ArrayType {
   435  	slice, ok := v.([]interface{})
   436  	if !ok || len(slice) == 0 {
   437  		return ArrayTypeOrdered
   438  	}
   439  	if isArrayItemType(slice[0]) {
   440  		return ArrayTypeAlternative
   441  	}
   442  	return ArrayTypeOrdered
   443  }