github.com/boki/go-xmp@v1.0.1/xmp/document.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  	"fmt"
    19  	"reflect"
    20  )
    21  
    22  type Document struct {
    23  	// name and version of the toolkit that generated an XMP document instance
    24  	toolkit string
    25  
    26  	nodes NodeList
    27  
    28  	// flag indicating the document content has changed
    29  	dirty bool
    30  
    31  	// rdf:about
    32  	about string
    33  
    34  	// local namespace map for tracking registered namespaces
    35  	intNsMap map[string]*Namespace
    36  
    37  	// local namespace map for tracking unknown namespaces
    38  	extNsMap map[string]*Namespace
    39  }
    40  
    41  // high-level XMP document interface
    42  func NewDocument() *Document {
    43  	d := &Document{
    44  		toolkit:  XMP_TOOLKIT_VERSION,
    45  		nodes:    make(NodeList, 0),
    46  		intNsMap: make(map[string]*Namespace),
    47  		extNsMap: make(map[string]*Namespace),
    48  	}
    49  	return d
    50  }
    51  
    52  func (d *Document) SetDirty() {
    53  	d.dirty = true
    54  }
    55  
    56  func (d Document) IsDirty() bool {
    57  	return d.dirty
    58  }
    59  
    60  func (d *Document) Close() {
    61  	if d == nil {
    62  		return
    63  	}
    64  	for _, v := range d.nodes {
    65  		v.Close()
    66  	}
    67  	d.nodes = nil
    68  }
    69  
    70  // cross-model sync, must be explicitly called to merge across models
    71  func (d *Document) SyncModels() error {
    72  	for _, n := range d.nodes {
    73  		if n.Model != nil {
    74  			if err := n.Model.SyncModel(d); err != nil {
    75  				return err
    76  			}
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  // implicit sync to align standard properties across models (e.g. xmp <-> photoshop)
    83  // called each time a model is loaded from XMP/XML or XMP/JSON
    84  func (d *Document) syncFromXMP() error {
    85  	for _, n := range d.nodes {
    86  		if n.Model != nil {
    87  			if err := n.Model.SyncFromXMP(d); err != nil {
    88  				return err
    89  			}
    90  		}
    91  	}
    92  	return nil
    93  }
    94  
    95  // called each time a model is stored as XMP/XML or XMP/JSON
    96  func (d *Document) syncToXMP() error {
    97  	if !d.dirty {
    98  		return nil
    99  	}
   100  	for _, n := range d.nodes {
   101  		if n.Model != nil {
   102  			if err := n.Model.SyncToXMP(d); err != nil {
   103  				return err
   104  			}
   105  		}
   106  	}
   107  	d.dirty = false
   108  	return nil
   109  }
   110  
   111  func (d *Document) Namespaces() NamespaceList {
   112  	var l NamespaceList
   113  	for _, v := range d.intNsMap {
   114  		l = append(l, v)
   115  	}
   116  	for _, v := range d.extNsMap {
   117  		l = append(l, v)
   118  	}
   119  	return l
   120  }
   121  
   122  func (d *Document) Nodes() NodeList {
   123  	return d.nodes
   124  }
   125  
   126  func (d *Document) FindNode(ns *Namespace) *Node {
   127  	return d.nodes.FindNode(ns)
   128  }
   129  
   130  func (d *Document) FindModel(ns *Namespace) Model {
   131  	prefix := ns.GetName()
   132  	for _, v := range d.nodes {
   133  		if v.Model != nil && v.Model.Can(prefix) {
   134  			return v.Model
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  func (d Document) FindNs(name, uri string) *Namespace {
   141  	var ns *Namespace
   142  	if uri != "" {
   143  		ns = d.findNsByURI(uri)
   144  	}
   145  	if ns == nil {
   146  		ns = d.findNsByPrefix(getPrefix(name))
   147  	}
   148  	return ns
   149  }
   150  
   151  func (d *Document) MakeModel(ns *Namespace) (Model, error) {
   152  	m := d.FindModel(ns)
   153  	if m == nil {
   154  		if m = ns.NewModel(); m == nil {
   155  			return nil, fmt.Errorf("xmp: cannot create '%s' model", ns.GetName())
   156  		}
   157  		n := NewNode(ns.XMLName(""))
   158  		n.Model = m
   159  		d.nodes = append(d.nodes, n)
   160  		for _, v := range m.Namespaces() {
   161  			d.intNsMap[v.GetURI()] = v
   162  		}
   163  		d.SetDirty()
   164  	}
   165  	return m, nil
   166  }
   167  
   168  func (d *Document) AddModel(v Model) (*Node, error) {
   169  	if v == nil {
   170  		return nil, fmt.Errorf("xmp: AddModel called with nil model")
   171  	}
   172  	ns := v.Namespaces()
   173  	if len(ns) == 0 {
   174  		return nil, fmt.Errorf("xmp: model '%s' must implement at least one namespace", reflect.TypeOf(v).String())
   175  	}
   176  	for _, v := range ns {
   177  		d.intNsMap[v.GetURI()] = v
   178  	}
   179  	n := d.FindNode(ns[0])
   180  	if n == nil {
   181  		n = NewNode(ns[0].XMLName(""))
   182  		d.nodes = append(d.nodes, n)
   183  	}
   184  	n.Model = v
   185  	d.SetDirty()
   186  	return n, nil
   187  }
   188  
   189  func (d *Document) RemoveNamespace(ns *Namespace) bool {
   190  	if ns == nil {
   191  		return false
   192  	}
   193  	name := ns.GetName()
   194  	removed := false
   195  	for i, v := range d.nodes {
   196  		if v.Name() == name {
   197  			d.nodes = append(d.nodes[:i], d.nodes[i+1:]...)
   198  			removed = true
   199  			delete(d.intNsMap, ns.GetURI())
   200  			delete(d.extNsMap, ns.GetURI())
   201  			v.Close()
   202  			d.SetDirty()
   203  			break
   204  		}
   205  	}
   206  	return removed
   207  }
   208  
   209  func (d *Document) RemoveNamespaceByName(n string) bool {
   210  	ns := d.findNsByPrefix(n)
   211  	return d.RemoveNamespace(ns)
   212  }
   213  
   214  // will delete all document nodes matching the list
   215  func (d *Document) RemoveNamespaces(remove NamespaceList) bool {
   216  	removed := false
   217  	for _, ns := range remove {
   218  		r := d.RemoveNamespace(ns)
   219  		removed = removed || r
   220  	}
   221  	return removed
   222  }
   223  
   224  // will keep document nodes matching the list and delete all others.
   225  // returns true if any namespace was removed.
   226  func (d *Document) FilterNamespaces(keep NamespaceList) bool {
   227  	removed := false
   228  	allNs := make(NamespaceList, 0)
   229  	// stage one: collect all top-level namespaces
   230  	for _, v := range d.nodes {
   231  		ns := d.findNsByPrefix(v.Namespace())
   232  		if ns != nil {
   233  			allNs = append(allNs, ns)
   234  		}
   235  	}
   236  	// stage two: remove
   237  	for _, v := range allNs {
   238  		if !keep.Contains(v) {
   239  			r := d.RemoveNamespace(v)
   240  			removed = removed || r
   241  		}
   242  	}
   243  	return removed
   244  }
   245  
   246  type DocumentArray []*Document
   247  
   248  func (x DocumentArray) Typ() ArrayType {
   249  	return ArrayTypeUnordered
   250  }
   251  
   252  func (x DocumentArray) MarshalXMP(e *Encoder, node *Node, m Model) error {
   253  	return MarshalArray(e, node, x.Typ(), x)
   254  }
   255  
   256  func (x *DocumentArray) UnmarshalXMP(d *Decoder, node *Node, m Model) error {
   257  	return UnmarshalArray(d, node, x.Typ(), x)
   258  }
   259  
   260  func (d Document) findNsByURI(uri string) *Namespace {
   261  	if v, ok := d.intNsMap[uri]; ok {
   262  		return v
   263  	}
   264  	if v, ok := d.extNsMap[uri]; ok {
   265  		return v
   266  	}
   267  	return nil
   268  }
   269  
   270  func (d Document) findNsByPrefix(pre string) *Namespace {
   271  	for _, v := range d.intNsMap {
   272  		if v.GetName() == pre {
   273  			return v
   274  		}
   275  	}
   276  	for _, v := range d.extNsMap {
   277  		if v.GetName() == pre {
   278  			return v
   279  		}
   280  	}
   281  	if ns, err := NsRegistry.GetNamespace(pre); err == nil {
   282  		return ns
   283  	}
   284  	return nil
   285  }