reader.go

Summary

Maintainability
A
1 hr
Test Coverage
package opc

import (
    "archive/zip"
    "encoding/xml"
    "fmt"
    "io"
    "os"
    "path"
    "strings"
)

type archiveFile interface {
    Open() (io.ReadCloser, error)
    Name() string
    Size() int
}

type archive interface {
    Files() []archiveFile
    RegisterDecompressor(method uint16, dcomp func(r io.Reader) io.ReadCloser)
}

// ReadCloser wrapps a Reader than can be closed.
type ReadCloser struct {
    f *os.File
    *Reader
}

// OpenReader will open the OPC file specified by name and return a ReadCloser.
func OpenReader(name string) (*ReadCloser, error) {
    f, err := os.Open(name)
    if err != nil {
        return nil, err
    }
    fi, err := f.Stat()
    if err != nil {
        f.Close()
        return nil, err
    }
    r, err := NewReader(f, fi.Size())
    return &ReadCloser{f: f, Reader: r}, err
}

// Close closes the OPC file, rendering it unusable for I/O.
func (r *ReadCloser) Close() error {
    return r.f.Close()
}

// File is used to read a part from the OPC package.
type File struct {
    *Part
    Size int
    a    archiveFile
}

// Open returns a ReadCloser that provides access to the File's contents.
// Multiple files may be read concurrently.
func (f *File) Open() (io.ReadCloser, error) {
    return f.a.Open()
}

// Reader implements a OPC file reader.
type Reader struct {
    Files         []*File
    Relationships []*Relationship
    Properties    CoreProperties
    p             *pkg
    r             archive
}

// NewReader returns a new Reader reading an OPC file to r.
func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
    zr, err := newZipReader(r, size)
    if err != nil {
        return nil, err
    }
    return newReader(zr)
}

// newReader returns a new Reader reading an OPC file to r.
func newReader(a archive) (*Reader, error) {
    r := &Reader{p: newPackage(), r: a}
    if err := r.loadPackage(); err != nil {
        return nil, err
    }
    return r, nil
}

// SetDecompressor sets or overrides a custom decompressor for the DEFLATE.
func (r *Reader) SetDecompressor(dcomp func(r io.Reader) io.ReadCloser) {
    r.r.RegisterDecompressor(zip.Deflate, dcomp)
}

func (r *Reader) loadPackage() error {
    ct, rels, err := r.loadPartProperties()
    if err != nil {
        return err
    }
    files := r.r.Files()
    r.Files = make([]*File, 0, len(files)-1) // -1 is for [Content_Types].xml

    for _, file := range files {
        fileName := "/" + file.Name()
        // skip content types part, relationship parts and directories
        if strings.EqualFold(fileName, contentTypesName) || isRelationshipURI(fileName) || strings.HasSuffix(fileName, "/") {
            continue
        }
        if strings.EqualFold(fileName, ResolveRelationship("/", r.Properties.PartName)) {
            err := r.loadCoreProperties(file)
            if err != nil {
                return err
            }
        } else {
            cType, err := ct.findType(NormalizePartName(fileName))
            if err != nil {
                return err
            }
            part := &Part{Name: fileName, ContentType: cType, Relationships: rels.findRelationship(fileName)}
            r.Files = append(r.Files, &File{part, file.Size(), file})
            if err = r.p.add(part); err != nil {
                return err
            }
        }
    }
    r.p.contentTypes = *ct
    return nil
}

func (r *Reader) loadPartProperties() (*contentTypes, *relationshipsPart, error) {
    var ct *contentTypes
    rels := new(relationshipsPart)
    for _, file := range r.r.Files() {
        var err error
        name := "/" + file.Name()
        if strings.EqualFold(name, contentTypesName) {
            ct, err = r.loadContentType(file)
        } else if isRelationshipURI(name) {
            if strings.EqualFold(name, packageRelName) {
                err = r.loadPackageRelationships(file)
            } else {
                err = loadRelationships(file, rels)
            }
        }
        if err != nil {
            return nil, nil, err
        }
    }
    if ct == nil {
        return nil, nil, newError(310, "/")
    }
    return ct, rels, nil
}

func (r *Reader) loadContentType(file archiveFile) (*contentTypes, error) {
    // Process descrived in ISO/IEC 29500-2 ยง10.1.2.4
    reader, err := file.Open()
    if err != nil {
        return nil, fmt.Errorf("opc: %s: cannot be opened: %v", contentTypesName, err)
    }
    return decodeContentTypes(reader)
}

func (r *Reader) loadCoreProperties(file archiveFile) error {
    reader, err := file.Open()
    if err != nil {
        return fmt.Errorf("opc: %s: cannot be opened: %v", r.Properties.PartName, err)
    }
    return decodeCoreProperties(reader, &r.Properties)
}

func loadRelationships(file archiveFile, rels *relationshipsPart) error {
    reader, err := file.Open()
    if err != nil {
        return fmt.Errorf("opc: %s: cannot be opened: %v", file.Name(), err)
    }
    rls, err := decodeRelationships(reader, file.Name())
    if err != nil {
        return err
    }

    // get part name from rels parts
    name := path.Dir(path.Dir(file.Name()))
    pname := "/" + name + "/" + strings.TrimSuffix(path.Base(file.Name()), path.Ext(file.Name()))
    pname = NormalizePartName(pname)
    rels.addRelationship(pname, rls)
    return nil
}

func (r *Reader) loadPackageRelationships(file archiveFile) error {
    reader, err := file.Open()
    if err != nil {
        return fmt.Errorf("opc: %s: cannot be opened: %v", file.Name(), err)
    }
    rls, err := decodeRelationships(reader, file.Name())
    if err != nil {
        return err
    }
    r.Relationships = rls
    for _, rel := range rls {
        if strings.EqualFold(rel.Type, corePropsRel) {
            r.Properties.PartName = rel.TargetURI
            r.Properties.RelationshipID = rel.ID
            break
        }
    }
    return nil
}

type contentTypesXMLReader struct {
    XMLName xml.Name `xml:"Types"`
    XML     string   `xml:"xmlns,attr"`
    Types   []mixed  `xml:",any"`
}

type mixed struct {
    Value interface{}
}

func (m *mixed) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    switch start.Name.Local {
    case "Override":
        var e overrideContentTypeXML
        if err := d.DecodeElement(&e, &start); err != nil {
            return err
        }
        m.Value = e
    case "Default":
        var e defaultContentTypeXML
        if err := d.DecodeElement(&e, &start); err != nil {
            return err
        }
        m.Value = e
    }
    return nil
}

func decodeContentTypes(r io.Reader) (*contentTypes, error) {
    ctdecode := new(contentTypesXMLReader)
    if err := xml.NewDecoder(r).Decode(ctdecode); err != nil {
        return nil, fmt.Errorf("opc: %s: cannot be decoded: %v", contentTypesName, err)
    }
    ct := new(contentTypes)
    for _, c := range ctdecode.Types {
        if cDefault, ok := c.Value.(defaultContentTypeXML); ok {
            ext := strings.ToLower(cDefault.Extension)
            if ext == "" {
                return nil, newError(206, "/")
            }
            if _, ok := ct.defaults[ext]; ok {
                return nil, newError(205, "/")
            }
            ct.addDefault(ext, cDefault.ContentType)
        } else if cOverride, ok := c.Value.(overrideContentTypeXML); ok {
            partName := strings.ToUpper(NormalizePartName(cOverride.PartName))
            if _, ok := ct.overrides[partName]; ok {
                return nil, newError(205, partName)
            }
            ct.addOverride(partName, cOverride.ContentType)
        }
    }
    return ct, nil
}