mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 09:12:24 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			651 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			651 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package exifcommon
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/dsoprea/go-logging"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	ifdLogger = log.NewLogger("exifcommon.ifd")
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
 | 
						|
)
 | 
						|
 | 
						|
// MappedIfd is one node in the IFD-mapping.
 | 
						|
type MappedIfd struct {
 | 
						|
	ParentTagId uint16
 | 
						|
	Placement   []uint16
 | 
						|
	Path        []string
 | 
						|
 | 
						|
	Name     string
 | 
						|
	TagId    uint16
 | 
						|
	Children map[uint16]*MappedIfd
 | 
						|
}
 | 
						|
 | 
						|
// String returns a descriptive string.
 | 
						|
func (mi *MappedIfd) String() string {
 | 
						|
	pathPhrase := mi.PathPhrase()
 | 
						|
	return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
 | 
						|
}
 | 
						|
 | 
						|
// PathPhrase returns a non-fully-qualified IFD path.
 | 
						|
func (mi *MappedIfd) PathPhrase() string {
 | 
						|
	return strings.Join(mi.Path, "/")
 | 
						|
}
 | 
						|
 | 
						|
// TODO(dustin): Refactor this to use IfdIdentity structs.
 | 
						|
 | 
						|
// IfdMapping describes all of the IFDs that we currently recognize.
 | 
						|
type IfdMapping struct {
 | 
						|
	rootNode *MappedIfd
 | 
						|
}
 | 
						|
 | 
						|
// NewIfdMapping returns a new IfdMapping struct.
 | 
						|
func NewIfdMapping() (ifdMapping *IfdMapping) {
 | 
						|
	rootNode := &MappedIfd{
 | 
						|
		Path:     make([]string, 0),
 | 
						|
		Children: make(map[uint16]*MappedIfd),
 | 
						|
	}
 | 
						|
 | 
						|
	return &IfdMapping{
 | 
						|
		rootNode: rootNode,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
 | 
						|
// standard IFDs.
 | 
						|
func NewIfdMappingWithStandard() (ifdMapping *IfdMapping, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	im := NewIfdMapping()
 | 
						|
 | 
						|
	err = LoadStandardIfds(im)
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	return im, nil
 | 
						|
}
 | 
						|
 | 
						|
// Get returns the node given the path slice.
 | 
						|
func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	ptr := im.rootNode
 | 
						|
	for _, tagId := range parentPlacement {
 | 
						|
		if descendantPtr, found := ptr.Children[tagId]; found == false {
 | 
						|
			log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
 | 
						|
		} else {
 | 
						|
			ptr = descendantPtr
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return ptr, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetWithPath returns the node given the path string.
 | 
						|
func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	if pathPhrase == "" {
 | 
						|
		log.Panicf("path-phrase is empty")
 | 
						|
	}
 | 
						|
 | 
						|
	path := strings.Split(pathPhrase, "/")
 | 
						|
	ptr := im.rootNode
 | 
						|
 | 
						|
	for _, name := range path {
 | 
						|
		var hit *MappedIfd
 | 
						|
		for _, mi := range ptr.Children {
 | 
						|
			if mi.Name == name {
 | 
						|
				hit = mi
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if hit == nil {
 | 
						|
			log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
 | 
						|
		}
 | 
						|
 | 
						|
		ptr = hit
 | 
						|
	}
 | 
						|
 | 
						|
	return ptr, nil
 | 
						|
}
 | 
						|
 | 
						|
// GetChild is a convenience function to get the child path for a given parent
 | 
						|
// placement and child tag-ID.
 | 
						|
func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	mi, err = im.GetWithPath(parentPathPhrase)
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	for _, childMi := range mi.Children {
 | 
						|
		if childMi.TagId == tagId {
 | 
						|
			return childMi, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Whether or not an IFD is defined in data, such an IFD is not registered
 | 
						|
	// and would be unknown.
 | 
						|
	log.Panic(ErrChildIfdNotMapped)
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// IfdTagIdAndIndex represents a specific part of the IFD path.
 | 
						|
//
 | 
						|
// This is a legacy type.
 | 
						|
type IfdTagIdAndIndex struct {
 | 
						|
	Name  string
 | 
						|
	TagId uint16
 | 
						|
	Index int
 | 
						|
}
 | 
						|
 | 
						|
// String returns a descriptive string.
 | 
						|
func (itii IfdTagIdAndIndex) String() string {
 | 
						|
	return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
 | 
						|
}
 | 
						|
 | 
						|
// ResolvePath takes a list of names, which can also be suffixed with indices
 | 
						|
// (to identify the second, third, etc.. sibling IFD) and returns a list of
 | 
						|
// tag-IDs and those indices.
 | 
						|
//
 | 
						|
// Example:
 | 
						|
//
 | 
						|
// - IFD/Exif/Iop
 | 
						|
// - IFD0/Exif/Iop
 | 
						|
//
 | 
						|
// This is the only call that supports adding the numeric indices.
 | 
						|
func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	pathPhrase = strings.TrimSpace(pathPhrase)
 | 
						|
 | 
						|
	if pathPhrase == "" {
 | 
						|
		log.Panicf("can not resolve empty path-phrase")
 | 
						|
	}
 | 
						|
 | 
						|
	path := strings.Split(pathPhrase, "/")
 | 
						|
	lineage = make([]IfdTagIdAndIndex, len(path))
 | 
						|
 | 
						|
	ptr := im.rootNode
 | 
						|
	empty := IfdTagIdAndIndex{}
 | 
						|
	for i, name := range path {
 | 
						|
		indexByte := name[len(name)-1]
 | 
						|
		index := 0
 | 
						|
		if indexByte >= '0' && indexByte <= '9' {
 | 
						|
			index = int(indexByte - '0')
 | 
						|
			name = name[:len(name)-1]
 | 
						|
		}
 | 
						|
 | 
						|
		itii := IfdTagIdAndIndex{}
 | 
						|
		for _, mi := range ptr.Children {
 | 
						|
			if mi.Name != name {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			itii.Name = name
 | 
						|
			itii.TagId = mi.TagId
 | 
						|
			itii.Index = index
 | 
						|
 | 
						|
			ptr = mi
 | 
						|
 | 
						|
			break
 | 
						|
		}
 | 
						|
 | 
						|
		if itii == empty {
 | 
						|
			log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
 | 
						|
		}
 | 
						|
 | 
						|
		lineage[i] = itii
 | 
						|
	}
 | 
						|
 | 
						|
	return lineage, nil
 | 
						|
}
 | 
						|
 | 
						|
// FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
 | 
						|
func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
 | 
						|
	fqPathParts := make([]string, len(lineage))
 | 
						|
	for i, itii := range lineage {
 | 
						|
		if itii.Index > 0 {
 | 
						|
			fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
 | 
						|
		} else {
 | 
						|
			fqPathParts[i] = itii.Name
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.Join(fqPathParts, "/")
 | 
						|
}
 | 
						|
 | 
						|
// PathPhraseFromLineage returns the non-fully-qualified IFD path from the
 | 
						|
// slice.
 | 
						|
func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
 | 
						|
	pathParts := make([]string, len(lineage))
 | 
						|
	for i, itii := range lineage {
 | 
						|
		pathParts[i] = itii.Name
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.Join(pathParts, "/")
 | 
						|
}
 | 
						|
 | 
						|
// StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
 | 
						|
// indices).
 | 
						|
func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	lineage, err := im.ResolvePath(pathPhrase)
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	strippedPathPhrase = im.PathPhraseFromLineage(lineage)
 | 
						|
	return strippedPathPhrase, nil
 | 
						|
}
 | 
						|
 | 
						|
// Add puts the given IFD at the given position of the tree. The position of the
 | 
						|
// tree is referred to as the placement and is represented by a set of tag-IDs,
 | 
						|
// where the leftmost is the root tag and the tags going to the right are
 | 
						|
// progressive descendants.
 | 
						|
func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
 | 
						|
 | 
						|
	ptr, err := im.Get(parentPlacement)
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	path := make([]string, len(parentPlacement)+1)
 | 
						|
	if len(parentPlacement) > 0 {
 | 
						|
		copy(path, ptr.Path)
 | 
						|
	}
 | 
						|
 | 
						|
	path[len(path)-1] = name
 | 
						|
 | 
						|
	placement := make([]uint16, len(parentPlacement)+1)
 | 
						|
	if len(placement) > 0 {
 | 
						|
		copy(placement, ptr.Placement)
 | 
						|
	}
 | 
						|
 | 
						|
	placement[len(placement)-1] = tagId
 | 
						|
 | 
						|
	childIfd := &MappedIfd{
 | 
						|
		ParentTagId: ptr.TagId,
 | 
						|
		Path:        path,
 | 
						|
		Placement:   placement,
 | 
						|
		Name:        name,
 | 
						|
		TagId:       tagId,
 | 
						|
		Children:    make(map[uint16]*MappedIfd),
 | 
						|
	}
 | 
						|
 | 
						|
	if _, found := ptr.Children[tagId]; found == true {
 | 
						|
		log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
 | 
						|
	}
 | 
						|
 | 
						|
	ptr.Children[tagId] = childIfd
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	currentIfd := stack[len(stack)-1]
 | 
						|
 | 
						|
	output = input
 | 
						|
	for _, childIfd := range currentIfd.Children {
 | 
						|
		stackCopy := make([]*MappedIfd, len(stack)+1)
 | 
						|
 | 
						|
		copy(stackCopy, stack)
 | 
						|
		stackCopy[len(stack)] = childIfd
 | 
						|
 | 
						|
		// Add to output, but don't include the obligatory root node.
 | 
						|
		parts := make([]string, len(stackCopy)-1)
 | 
						|
		for i, mi := range stackCopy[1:] {
 | 
						|
			parts[i] = mi.Name
 | 
						|
		}
 | 
						|
 | 
						|
		output = append(output, strings.Join(parts, "/"))
 | 
						|
 | 
						|
		output, err = im.dumpLineages(stackCopy, output)
 | 
						|
		log.PanicIf(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return output, nil
 | 
						|
}
 | 
						|
 | 
						|
// DumpLineages returns a slice of strings representing all mappings.
 | 
						|
func (im *IfdMapping) DumpLineages() (output []string, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	stack := []*MappedIfd{im.rootNode}
 | 
						|
	output = make([]string, 0)
 | 
						|
 | 
						|
	output, err = im.dumpLineages(stack, output)
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	return output, nil
 | 
						|
}
 | 
						|
 | 
						|
// LoadStandardIfds loads the standard IFDs into the mapping.
 | 
						|
func LoadStandardIfds(im *IfdMapping) (err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	err = im.Add(
 | 
						|
		[]uint16{},
 | 
						|
		IfdStandardIfdIdentity.TagId(), IfdStandardIfdIdentity.Name())
 | 
						|
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	err = im.Add(
 | 
						|
		[]uint16{IfdStandardIfdIdentity.TagId()},
 | 
						|
		IfdExifStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.Name())
 | 
						|
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	err = im.Add(
 | 
						|
		[]uint16{IfdStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.TagId()},
 | 
						|
		IfdExifIopStandardIfdIdentity.TagId(), IfdExifIopStandardIfdIdentity.Name())
 | 
						|
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	err = im.Add(
 | 
						|
		[]uint16{IfdStandardIfdIdentity.TagId()},
 | 
						|
		IfdGpsInfoStandardIfdIdentity.TagId(), IfdGpsInfoStandardIfdIdentity.Name())
 | 
						|
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// IfdTag describes a single IFD tag and its parent (if any).
 | 
						|
type IfdTag struct {
 | 
						|
	parentIfdTag *IfdTag
 | 
						|
	tagId        uint16
 | 
						|
	name         string
 | 
						|
}
 | 
						|
 | 
						|
func NewIfdTag(parentIfdTag *IfdTag, tagId uint16, name string) IfdTag {
 | 
						|
	return IfdTag{
 | 
						|
		parentIfdTag: parentIfdTag,
 | 
						|
		tagId:        tagId,
 | 
						|
		name:         name,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ParentIfd returns the IfdTag of this IFD's parent.
 | 
						|
func (it IfdTag) ParentIfd() *IfdTag {
 | 
						|
	return it.parentIfdTag
 | 
						|
}
 | 
						|
 | 
						|
// TagId returns the tag-ID of this IFD.
 | 
						|
func (it IfdTag) TagId() uint16 {
 | 
						|
	return it.tagId
 | 
						|
}
 | 
						|
 | 
						|
// Name returns the simple name of this IFD.
 | 
						|
func (it IfdTag) Name() string {
 | 
						|
	return it.name
 | 
						|
}
 | 
						|
 | 
						|
// String returns a descriptive string.
 | 
						|
func (it IfdTag) String() string {
 | 
						|
	parentIfdPhrase := ""
 | 
						|
	if it.parentIfdTag != nil {
 | 
						|
		parentIfdPhrase = fmt.Sprintf(" PARENT=(0x%04x)[%s]", it.parentIfdTag.tagId, it.parentIfdTag.name)
 | 
						|
	}
 | 
						|
 | 
						|
	return fmt.Sprintf("IfdTag<TAG-ID=(0x%04x) NAME=[%s]%s>", it.tagId, it.name, parentIfdPhrase)
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	// rootStandardIfd is the standard root IFD.
 | 
						|
	rootStandardIfd = NewIfdTag(nil, 0x0000, "IFD") // IFD
 | 
						|
 | 
						|
	// exifStandardIfd is the standard "Exif" IFD.
 | 
						|
	exifStandardIfd = NewIfdTag(&rootStandardIfd, 0x8769, "Exif") // IFD/Exif
 | 
						|
 | 
						|
	// iopStandardIfd is the standard "Iop" IFD.
 | 
						|
	iopStandardIfd = NewIfdTag(&exifStandardIfd, 0xA005, "Iop") // IFD/Exif/Iop
 | 
						|
 | 
						|
	// gpsInfoStandardIfd is the standard "GPS" IFD.
 | 
						|
	gpsInfoStandardIfd = NewIfdTag(&rootStandardIfd, 0x8825, "GPSInfo") // IFD/GPSInfo
 | 
						|
)
 | 
						|
 | 
						|
// IfdIdentityPart represents one component in an IFD path.
 | 
						|
type IfdIdentityPart struct {
 | 
						|
	Name  string
 | 
						|
	Index int
 | 
						|
}
 | 
						|
 | 
						|
// String returns a fully-qualified IFD path.
 | 
						|
func (iip IfdIdentityPart) String() string {
 | 
						|
	if iip.Index > 0 {
 | 
						|
		return fmt.Sprintf("%s%d", iip.Name, iip.Index)
 | 
						|
	} else {
 | 
						|
		return iip.Name
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// UnindexedString returned a non-fully-qualified IFD path.
 | 
						|
func (iip IfdIdentityPart) UnindexedString() string {
 | 
						|
	return iip.Name
 | 
						|
}
 | 
						|
 | 
						|
// IfdIdentity represents a single IFD path and provides access to various
 | 
						|
// information and representations.
 | 
						|
//
 | 
						|
// Only global instances can be used for equality checks.
 | 
						|
type IfdIdentity struct {
 | 
						|
	ifdTag    IfdTag
 | 
						|
	parts     []IfdIdentityPart
 | 
						|
	ifdPath   string
 | 
						|
	fqIfdPath string
 | 
						|
}
 | 
						|
 | 
						|
// NewIfdIdentity returns a new IfdIdentity struct.
 | 
						|
func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) {
 | 
						|
	ii = &IfdIdentity{
 | 
						|
		ifdTag: ifdTag,
 | 
						|
		parts:  parts,
 | 
						|
	}
 | 
						|
 | 
						|
	ii.ifdPath = ii.getIfdPath()
 | 
						|
	ii.fqIfdPath = ii.getFqIfdPath()
 | 
						|
 | 
						|
	return ii
 | 
						|
}
 | 
						|
 | 
						|
// NewIfdIdentityFromString parses a string like "IFD/Exif" or "IFD1" or
 | 
						|
// something more exotic with custom IFDs ("SomeIFD4/SomeChildIFD6"). Note that
 | 
						|
// this will valid the unindexed IFD structure (because the standard tags from
 | 
						|
// the specification are unindexed), but not, obviously, any indices (e.g.
 | 
						|
// the numbers in "IFD0", "IFD1", "SomeIFD4/SomeChildIFD6"). It is
 | 
						|
// required for the caller to check whether these specific instances
 | 
						|
// were actually parsed out of the stream.
 | 
						|
func NewIfdIdentityFromString(im *IfdMapping, fqIfdPath string) (ii *IfdIdentity, err error) {
 | 
						|
	defer func() {
 | 
						|
		if state := recover(); state != nil {
 | 
						|
			err = log.Wrap(state.(error))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	lineage, err := im.ResolvePath(fqIfdPath)
 | 
						|
	log.PanicIf(err)
 | 
						|
 | 
						|
	var lastIt *IfdTag
 | 
						|
	identityParts := make([]IfdIdentityPart, len(lineage))
 | 
						|
	for i, itii := range lineage {
 | 
						|
		// Build out the tag that will eventually point to the IFD represented
 | 
						|
		// by the right-most part in the IFD path.
 | 
						|
 | 
						|
		it := &IfdTag{
 | 
						|
			parentIfdTag: lastIt,
 | 
						|
			tagId:        itii.TagId,
 | 
						|
			name:         itii.Name,
 | 
						|
		}
 | 
						|
 | 
						|
		lastIt = it
 | 
						|
 | 
						|
		// Create the next IfdIdentity part.
 | 
						|
 | 
						|
		iip := IfdIdentityPart{
 | 
						|
			Name:  itii.Name,
 | 
						|
			Index: itii.Index,
 | 
						|
		}
 | 
						|
 | 
						|
		identityParts[i] = iip
 | 
						|
	}
 | 
						|
 | 
						|
	ii = NewIfdIdentity(*lastIt, identityParts...)
 | 
						|
	return ii, nil
 | 
						|
}
 | 
						|
 | 
						|
func (ii *IfdIdentity) getFqIfdPath() string {
 | 
						|
	partPhrases := make([]string, len(ii.parts))
 | 
						|
	for i, iip := range ii.parts {
 | 
						|
		partPhrases[i] = iip.String()
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.Join(partPhrases, "/")
 | 
						|
}
 | 
						|
 | 
						|
func (ii *IfdIdentity) getIfdPath() string {
 | 
						|
	partPhrases := make([]string, len(ii.parts))
 | 
						|
	for i, iip := range ii.parts {
 | 
						|
		partPhrases[i] = iip.UnindexedString()
 | 
						|
	}
 | 
						|
 | 
						|
	return strings.Join(partPhrases, "/")
 | 
						|
}
 | 
						|
 | 
						|
// String returns a fully-qualified IFD path.
 | 
						|
func (ii *IfdIdentity) String() string {
 | 
						|
	return ii.fqIfdPath
 | 
						|
}
 | 
						|
 | 
						|
// UnindexedString returns a non-fully-qualified IFD path.
 | 
						|
func (ii *IfdIdentity) UnindexedString() string {
 | 
						|
	return ii.ifdPath
 | 
						|
}
 | 
						|
 | 
						|
// IfdTag returns the tag struct behind this IFD.
 | 
						|
func (ii *IfdIdentity) IfdTag() IfdTag {
 | 
						|
	return ii.ifdTag
 | 
						|
}
 | 
						|
 | 
						|
// TagId returns the tag-ID of the IFD.
 | 
						|
func (ii *IfdIdentity) TagId() uint16 {
 | 
						|
	return ii.ifdTag.TagId()
 | 
						|
}
 | 
						|
 | 
						|
// LeafPathPart returns the last right-most path-part, which represents the
 | 
						|
// current IFD.
 | 
						|
func (ii *IfdIdentity) LeafPathPart() IfdIdentityPart {
 | 
						|
	return ii.parts[len(ii.parts)-1]
 | 
						|
}
 | 
						|
 | 
						|
// Name returns the simple name of this IFD.
 | 
						|
func (ii *IfdIdentity) Name() string {
 | 
						|
	return ii.LeafPathPart().Name
 | 
						|
}
 | 
						|
 | 
						|
// Index returns the index of this IFD (more then one IFD under a parent IFD
 | 
						|
// will be numbered [0..n]).
 | 
						|
func (ii *IfdIdentity) Index() int {
 | 
						|
	return ii.LeafPathPart().Index
 | 
						|
}
 | 
						|
 | 
						|
// Equals returns true if the two IfdIdentity instances are effectively
 | 
						|
// identical.
 | 
						|
//
 | 
						|
// Since there's no way to get a specific fully-qualified IFD path without a
 | 
						|
// certain slice of parts and all other fields are also derived from this,
 | 
						|
// checking that the fully-qualified IFD path is equals is sufficient.
 | 
						|
func (ii *IfdIdentity) Equals(ii2 *IfdIdentity) bool {
 | 
						|
	return ii.String() == ii2.String()
 | 
						|
}
 | 
						|
 | 
						|
// NewChild creates an IfdIdentity for an IFD that is a child of the current
 | 
						|
// IFD.
 | 
						|
func (ii *IfdIdentity) NewChild(childIfdTag IfdTag, index int) (iiChild *IfdIdentity) {
 | 
						|
	if *childIfdTag.parentIfdTag != ii.ifdTag {
 | 
						|
		log.Panicf("can not add child; we are not the parent:\nUS=%v\nCHILD=%v", ii.ifdTag, childIfdTag)
 | 
						|
	}
 | 
						|
 | 
						|
	childPart := IfdIdentityPart{childIfdTag.name, index}
 | 
						|
	childParts := append(ii.parts, childPart)
 | 
						|
 | 
						|
	iiChild = NewIfdIdentity(childIfdTag, childParts...)
 | 
						|
	return iiChild
 | 
						|
}
 | 
						|
 | 
						|
// NewSibling creates an IfdIdentity for an IFD that is a sibling to the current
 | 
						|
// one.
 | 
						|
func (ii *IfdIdentity) NewSibling(index int) (iiSibling *IfdIdentity) {
 | 
						|
	parts := make([]IfdIdentityPart, len(ii.parts))
 | 
						|
 | 
						|
	copy(parts, ii.parts)
 | 
						|
	parts[len(parts)-1].Index = index
 | 
						|
 | 
						|
	iiSibling = NewIfdIdentity(ii.ifdTag, parts...)
 | 
						|
	return iiSibling
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	// IfdStandardIfdIdentity represents the IFD path for IFD0.
 | 
						|
	IfdStandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 0})
 | 
						|
 | 
						|
	// IfdExifStandardIfdIdentity represents the IFD path for IFD0/Exif0.
 | 
						|
	IfdExifStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(exifStandardIfd, 0)
 | 
						|
 | 
						|
	// IfdExifIopStandardIfdIdentity represents the IFD path for IFD0/Exif0/Iop0.
 | 
						|
	IfdExifIopStandardIfdIdentity = IfdExifStandardIfdIdentity.NewChild(iopStandardIfd, 0)
 | 
						|
 | 
						|
	// IfdGPSInfoStandardIfdIdentity represents the IFD path for IFD0/GPSInfo0.
 | 
						|
	IfdGpsInfoStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(gpsInfoStandardIfd, 0)
 | 
						|
 | 
						|
	// Ifd1StandardIfdIdentity represents the IFD path for IFD1.
 | 
						|
	Ifd1StandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 1})
 | 
						|
)
 |