From 2d49fce8c016bcbe218433f88ae9a72e21c21653 Mon Sep 17 00:00:00 2001 From: "ron@tinyerp.com" <> Date: Wed, 5 Jan 2011 12:17:56 +0530 Subject: [PATCH 01/50] [FIX]report_intrastat:Belgium not defined as an Intrastat member by default: lp bug: https://launchpad.net/bugs/697209 fixed bzr revid: ron@tinyerp.com-20110105064756-1jbnvmpa24fc1oxc --- addons/report_intrastat/report_intrastat_data.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/report_intrastat/report_intrastat_data.xml b/addons/report_intrastat/report_intrastat_data.xml index f3c465c1549..006151915e2 100644 --- a/addons/report_intrastat/report_intrastat_data.xml +++ b/addons/report_intrastat/report_intrastat_data.xml @@ -4,6 +4,9 @@ + + + From 73af237c8b40dd6f57b54273f731576713a877ba Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 6 Jan 2011 13:14:00 +0200 Subject: [PATCH 02/50] pyPdf: upgrade from upstream git: 4abdca42a7d8a4 bzr revid: p_christ@hol.gr-20110106111400-hqw1nu5wx1mict4t --- bin/report/pyPdf/__init__.py | 1 - bin/report/pyPdf/filters.py | 505 +++++++++++++------------ bin/report/pyPdf/generic.py | 35 +- bin/report/pyPdf/pdf.py | 418 ++++++++++++++++++-- bin/report/pyPdf/utils.py | 233 ++++++------ bin/report/pyPdf/xmp.py | 711 +++++++++++++++++------------------ 6 files changed, 1136 insertions(+), 767 deletions(-) diff --git a/bin/report/pyPdf/__init__.py b/bin/report/pyPdf/__init__.py index 28360a93847..af02553da69 100644 --- a/bin/report/pyPdf/__init__.py +++ b/bin/report/pyPdf/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- from pdf import PdfFileReader, PdfFileWriter __all__ = ["pdf"] diff --git a/bin/report/pyPdf/filters.py b/bin/report/pyPdf/filters.py index 04bb7d9df55..7fe10fb4819 100644 --- a/bin/report/pyPdf/filters.py +++ b/bin/report/pyPdf/filters.py @@ -1,253 +1,252 @@ -# -*- coding: utf-8 -*- -# vim: sw=4:expandtab:foldmethod=marker -# -# Copyright (c) 2006, Mathieu Fenniak -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - - -""" -Implementation of stream filters for PDF. -""" -__author__ = "Mathieu Fenniak" -__author_email__ = "biziqe@mathieu.fenniak.net" - -from utils import PdfReadError -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - -try: - import zlib - def decompress(data): - return zlib.decompress(data) - def compress(data): - return zlib.compress(data) -except ImportError: - # Unable to import zlib. Attempt to use the System.IO.Compression - # library from the .NET framework. (IronPython only) - import System - from System import IO, Collections, Array - def _string_to_bytearr(buf): - retval = Array.CreateInstance(System.Byte, len(buf)) - for i in range(len(buf)): - retval[i] = ord(buf[i]) - return retval - def _bytearr_to_string(bytes): - retval = "" - for i in range(bytes.Length): - retval += chr(bytes[i]) - return retval - def _read_bytes(stream): - ms = IO.MemoryStream() - buf = Array.CreateInstance(System.Byte, 2048) - while True: - bytes = stream.Read(buf, 0, buf.Length) - if bytes == 0: - break - else: - ms.Write(buf, 0, bytes) - retval = ms.ToArray() - ms.Close() - return retval - def decompress(data): - bytes = _string_to_bytearr(data) - ms = IO.MemoryStream() - ms.Write(bytes, 0, bytes.Length) - ms.Position = 0 # fseek 0 - gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress) - bytes = _read_bytes(gz) - retval = _bytearr_to_string(bytes) - gz.Close() - return retval - def compress(data): - bytes = _string_to_bytearr(data) - ms = IO.MemoryStream() - gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True) - gz.Write(bytes, 0, bytes.Length) - gz.Close() - ms.Position = 0 # fseek 0 - bytes = ms.ToArray() - retval = _bytearr_to_string(bytes) - ms.Close() - return retval - - -class FlateDecode(object): - def decode(data, decodeParms): - data = decompress(data) - predictor = 1 - if decodeParms: - predictor = decodeParms.get("/Predictor", 1) - # predictor 1 == no predictor - if predictor != 1: - columns = decodeParms["/Columns"] - # PNG prediction: - if predictor >= 10 and predictor <= 15: - output = StringIO() - # PNG prediction can vary from row to row - rowlength = columns + 1 - assert len(data) % rowlength == 0 - prev_rowdata = (0,) * rowlength - for row in xrange(len(data) / rowlength): - rowdata = [ord(x) for x in data[(row*rowlength):((row+1)*rowlength)]] - filterByte = rowdata[0] - if filterByte == 0: - pass - elif filterByte == 1: - for i in range(2, rowlength): - rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256 - elif filterByte == 2: - for i in range(1, rowlength): - rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256 - else: - # unsupported PNG filter - raise PdfReadError("Unsupported PNG filter %r" % filterByte) - prev_rowdata = rowdata - output.write(''.join([chr(x) for x in rowdata[1:]])) - data = output.getvalue() - else: - # unsupported predictor - raise PdfReadError("Unsupported flatedecode predictor %r" % predictor) - return data - decode = staticmethod(decode) - - def encode(data): - return compress(data) - encode = staticmethod(encode) - -class ASCIIHexDecode(object): - def decode(data, decodeParms=None): - retval = "" - char = "" - x = 0 - while True: - c = data[x] - if c == ">": - break - elif c.isspace(): - x += 1 - continue - char += c - if len(char) == 2: - retval += chr(int(char, base=16)) - char = "" - x += 1 - assert char == "" - return retval - decode = staticmethod(decode) - -class ASCII85Decode(object): - def decode(data, decodeParms=None): - retval = "" - group = [] - x = 0 - hitEod = False - # remove all whitespace from data - data = [y for y in data if not (y in ' \n\r\t')] - while not hitEod: - c = data[x] - if len(retval) == 0 and c == "<" and data[x+1] == "~": - x += 2 - continue - #elif c.isspace(): - # x += 1 - # continue - elif c == 'z': - assert len(group) == 0 - retval += '\x00\x00\x00\x00' - continue - elif c == "~" and data[x+1] == ">": - if len(group) != 0: - # cannot have a final group of just 1 char - assert len(group) > 1 - cnt = len(group) - 1 - group += [ 85, 85, 85 ] - hitEod = cnt - else: - break - else: - c = ord(c) - 33 - assert c >= 0 and c < 85 - group += [ c ] - if len(group) >= 5: - b = group[0] * (85**4) + \ - group[1] * (85**3) + \ - group[2] * (85**2) + \ - group[3] * 85 + \ - group[4] - assert b < (2**32 - 1) - c4 = chr((b >> 0) % 256) - c3 = chr((b >> 8) % 256) - c2 = chr((b >> 16) % 256) - c1 = chr(b >> 24) - retval += (c1 + c2 + c3 + c4) - if hitEod: - retval = retval[:-4+hitEod] - group = [] - x += 1 - return retval - decode = staticmethod(decode) - -def decodeStreamData(stream): - from generic import NameObject - filters = stream.get("/Filter", ()) - if len(filters) and not isinstance(filters[0], NameObject): - # we have a single filter instance - filters = (filters,) - data = stream._data - for filterType in filters: - if filterType == "/FlateDecode": - data = FlateDecode.decode(data, stream.get("/DecodeParms")) - elif filterType == "/ASCIIHexDecode": - data = ASCIIHexDecode.decode(data) - elif filterType == "/ASCII85Decode": - data = ASCII85Decode.decode(data) - elif filterType == "/Crypt": - decodeParams = stream.get("/DecodeParams", {}) - if "/Name" not in decodeParams and "/Type" not in decodeParams: - pass - else: - raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet") - else: - # unsupported filter - raise NotImplementedError("unsupported filter %s" % filterType) - return data - -if __name__ == "__main__": - assert "abc" == ASCIIHexDecode.decode('61\n626\n3>') - - ascii85Test = """ - <~9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKFCj@.4Gp$d7F!,L7@<6@)/0JDEF@3BB/F*&OCAfu2/AKY - i(DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIa - l(DIduD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c~> - """ - ascii85_originalText="Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure." - assert ASCII85Decode.decode(ascii85Test) == ascii85_originalText - +# vim: sw=4:expandtab:foldmethod=marker +# +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +""" +Implementation of stream filters for PDF. +""" +__author__ = "Mathieu Fenniak" +__author_email__ = "biziqe@mathieu.fenniak.net" + +from utils import PdfReadError +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +try: + import zlib + def decompress(data): + return zlib.decompress(data) + def compress(data): + return zlib.compress(data) +except ImportError: + # Unable to import zlib. Attempt to use the System.IO.Compression + # library from the .NET framework. (IronPython only) + import System + from System import IO, Collections, Array + def _string_to_bytearr(buf): + retval = Array.CreateInstance(System.Byte, len(buf)) + for i in range(len(buf)): + retval[i] = ord(buf[i]) + return retval + def _bytearr_to_string(bytes): + retval = "" + for i in range(bytes.Length): + retval += chr(bytes[i]) + return retval + def _read_bytes(stream): + ms = IO.MemoryStream() + buf = Array.CreateInstance(System.Byte, 2048) + while True: + bytes = stream.Read(buf, 0, buf.Length) + if bytes == 0: + break + else: + ms.Write(buf, 0, bytes) + retval = ms.ToArray() + ms.Close() + return retval + def decompress(data): + bytes = _string_to_bytearr(data) + ms = IO.MemoryStream() + ms.Write(bytes, 0, bytes.Length) + ms.Position = 0 # fseek 0 + gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress) + bytes = _read_bytes(gz) + retval = _bytearr_to_string(bytes) + gz.Close() + return retval + def compress(data): + bytes = _string_to_bytearr(data) + ms = IO.MemoryStream() + gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True) + gz.Write(bytes, 0, bytes.Length) + gz.Close() + ms.Position = 0 # fseek 0 + bytes = ms.ToArray() + retval = _bytearr_to_string(bytes) + ms.Close() + return retval + + +class FlateDecode(object): + def decode(data, decodeParms): + data = decompress(data) + predictor = 1 + if decodeParms: + predictor = decodeParms.get("/Predictor", 1) + # predictor 1 == no predictor + if predictor != 1: + columns = decodeParms["/Columns"] + # PNG prediction: + if predictor >= 10 and predictor <= 15: + output = StringIO() + # PNG prediction can vary from row to row + rowlength = columns + 1 + assert len(data) % rowlength == 0 + prev_rowdata = (0,) * rowlength + for row in xrange(len(data) / rowlength): + rowdata = [ord(x) for x in data[(row*rowlength):((row+1)*rowlength)]] + filterByte = rowdata[0] + if filterByte == 0: + pass + elif filterByte == 1: + for i in range(2, rowlength): + rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256 + elif filterByte == 2: + for i in range(1, rowlength): + rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256 + else: + # unsupported PNG filter + raise PdfReadError("Unsupported PNG filter %r" % filterByte) + prev_rowdata = rowdata + output.write(''.join([chr(x) for x in rowdata[1:]])) + data = output.getvalue() + else: + # unsupported predictor + raise PdfReadError("Unsupported flatedecode predictor %r" % predictor) + return data + decode = staticmethod(decode) + + def encode(data): + return compress(data) + encode = staticmethod(encode) + +class ASCIIHexDecode(object): + def decode(data, decodeParms=None): + retval = "" + char = "" + x = 0 + while True: + c = data[x] + if c == ">": + break + elif c.isspace(): + x += 1 + continue + char += c + if len(char) == 2: + retval += chr(int(char, base=16)) + char = "" + x += 1 + assert char == "" + return retval + decode = staticmethod(decode) + +class ASCII85Decode(object): + def decode(data, decodeParms=None): + retval = "" + group = [] + x = 0 + hitEod = False + # remove all whitespace from data + data = [y for y in data if not (y in ' \n\r\t')] + while not hitEod: + c = data[x] + if len(retval) == 0 and c == "<" and data[x+1] == "~": + x += 2 + continue + #elif c.isspace(): + # x += 1 + # continue + elif c == 'z': + assert len(group) == 0 + retval += '\x00\x00\x00\x00' + continue + elif c == "~" and data[x+1] == ">": + if len(group) != 0: + # cannot have a final group of just 1 char + assert len(group) > 1 + cnt = len(group) - 1 + group += [ 85, 85, 85 ] + hitEod = cnt + else: + break + else: + c = ord(c) - 33 + assert c >= 0 and c < 85 + group += [ c ] + if len(group) >= 5: + b = group[0] * (85**4) + \ + group[1] * (85**3) + \ + group[2] * (85**2) + \ + group[3] * 85 + \ + group[4] + assert b < (2**32 - 1) + c4 = chr((b >> 0) % 256) + c3 = chr((b >> 8) % 256) + c2 = chr((b >> 16) % 256) + c1 = chr(b >> 24) + retval += (c1 + c2 + c3 + c4) + if hitEod: + retval = retval[:-4+hitEod] + group = [] + x += 1 + return retval + decode = staticmethod(decode) + +def decodeStreamData(stream): + from generic import NameObject + filters = stream.get("/Filter", ()) + if len(filters) and not isinstance(filters[0], NameObject): + # we have a single filter instance + filters = (filters,) + data = stream._data + for filterType in filters: + if filterType == "/FlateDecode": + data = FlateDecode.decode(data, stream.get("/DecodeParms")) + elif filterType == "/ASCIIHexDecode": + data = ASCIIHexDecode.decode(data) + elif filterType == "/ASCII85Decode": + data = ASCII85Decode.decode(data) + elif filterType == "/Crypt": + decodeParams = stream.get("/DecodeParams", {}) + if "/Name" not in decodeParams and "/Type" not in decodeParams: + pass + else: + raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet") + else: + # unsupported filter + raise NotImplementedError("unsupported filter %s" % filterType) + return data + +if __name__ == "__main__": + assert "abc" == ASCIIHexDecode.decode('61\n626\n3>') + + ascii85Test = """ + <~9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKFCj@.4Gp$d7F!,L7@<6@)/0JDEF@3BB/F*&OCAfu2/AKY + i(DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIa + l(DIduD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c~> + """ + ascii85_originalText="Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure." + assert ASCII85Decode.decode(ascii85Test) == ascii85_originalText + diff --git a/bin/report/pyPdf/generic.py b/bin/report/pyPdf/generic.py index f216732a85d..aaf503180f8 100644 --- a/bin/report/pyPdf/generic.py +++ b/bin/report/pyPdf/generic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # vim: sw=4:expandtab:foldmethod=marker # # Copyright (c) 2006, Mathieu Fenniak @@ -207,15 +206,18 @@ class FloatObject(decimal.Decimal, PdfObject): def __new__(cls, value="0", context=None): return decimal.Decimal.__new__(cls, str(value), context) def __repr__(self): - return str(self) + if self == self.to_integral(): + return str(self.quantize(decimal.Decimal(1))) + else: + # XXX: this adds useless extraneous zeros. + return "%.5f" % self def writeToStream(self, stream, encryption_key): - stream.write(str(self)) + stream.write(repr(self)) class NumberObject(int, PdfObject): def __init__(self, value): - int.__init__(self) - self = value + int.__init__(value) def writeToStream(self, stream, encryption_key): stream.write(repr(self)) @@ -301,7 +303,7 @@ def readStringFromStream(stream): elif tok == "t": tok = "\t" elif tok == "b": - tok == "\b" + tok = "\b" elif tok == "f": tok = "\f" elif tok == "(": @@ -311,7 +313,17 @@ def readStringFromStream(stream): elif tok == "\\": tok = "\\" elif tok.isdigit(): - tok += stream.read(2) + # "The number ddd may consist of one, two, or three + # octal digits; high-order overflow shall be ignored. + # Three octal digits shall be used, with leading zeros + # as needed, if the next character of the string is also + # a digit." (PDF reference 7.3.4.2, p 16) + for i in range(2): + ntok = stream.read(1) + if ntok.isdigit(): + tok += ntok + else: + break tok = chr(int(tok, base=8)) elif tok in "\n\r": # This case is hit when a backslash followed by a line @@ -405,8 +417,7 @@ class NameObject(str, PdfObject): delimiterCharacters = "(", ")", "<", ">", "[", "]", "{", "}", "/", "%" def __init__(self, data): - str.__init__(self) - self = data + str.__init__(data) def writeToStream(self, stream, encryption_key): stream.write(self) @@ -710,6 +721,12 @@ class RectangleObject(ArrayObject): def setUpperRight(self, value): self[2], self[3] = [self.ensureIsNumber(x) for x in value] + def getWidth(self): + return self.getUpperRight_x() - self.getLowerLeft_x() + + def getHeight(self): + return self.getUpperRight_y() - self.getLowerLeft_x() + lowerLeft = property(getLowerLeft, setLowerLeft, None, None) lowerRight = property(getLowerRight, setLowerRight, None, None) upperLeft = property(getUpperLeft, setUpperLeft, None, None) diff --git a/bin/report/pyPdf/pdf.py b/bin/report/pyPdf/pdf.py index 9bc56cf8b05..bf60d0157dc 100644 --- a/bin/report/pyPdf/pdf.py +++ b/bin/report/pyPdf/pdf.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# # vim: sw=4:expandtab:foldmethod=marker # # Copyright (c) 2006, Mathieu Fenniak @@ -39,7 +40,9 @@ It may be a solid base for future PDF file work in Python. __author__ = "Mathieu Fenniak" __author_email__ = "biziqe@mathieu.fenniak.net" +import math import struct +from sys import version_info try: from cStringIO import StringIO except ImportError: @@ -51,6 +54,14 @@ import warnings from generic import * from utils import readNonWhitespace, readUntilWhitespace, ConvertFunctionsToVirtualList +if version_info < ( 2, 4 ): + from sets import ImmutableSet as frozenset + +if version_info < ( 2, 5 ): + from md5 import md5 +else: + from hashlib import md5 + ## # This class supports writing PDF files out, given pages produced by another # class (typically {@link #PdfFileReader PdfFileReader}). @@ -92,6 +103,21 @@ class PdfFileWriter(object): raise ValueError("pdf must be self") return self._objects[ido.idnum - 1] + ## + # Common method for inserting or adding a page to this PDF file. + # + # @param page The page to add to the document. This argument should be + # an instance of {@link #PageObject PageObject}. + # @param action The function which will insert the page in the dictionnary. + # Takes: page list, page to add. + def _addPage(self, page, action): + assert page["/Type"] == "/Page" + page[NameObject("/Parent")] = self._pages + page = self._addObject(page) + pages = self.getObject(self._pages) + action(pages["/Kids"], page) + pages[NameObject("/Count")] = NumberObject(pages["/Count"] + 1) + ## # Adds a page to this PDF file. The page is usually acquired from a # {@link #PdfFileReader PdfFileReader} instance. @@ -101,12 +127,64 @@ class PdfFileWriter(object): # @param page The page to add to the document. This argument should be # an instance of {@link #PageObject PageObject}. def addPage(self, page): - assert page["/Type"] == "/Page" - page[NameObject("/Parent")] = self._pages - page = self._addObject(page) + self._addPage(page, list.append) + + ## + # Insert a page in this PDF file. The page is usually acquired from a + # {@link #PdfFileReader PdfFileReader} instance. + # + # @param page The page to add to the document. This argument should be + # an instance of {@link #PageObject PageObject}. + # @param index Position at which the page will be inserted. + def insertPage(self, page, index=0): + self._addPage(page, lambda l, p: l.insert(index, p)) + + ## + # Retrieves a page by number from this PDF file. + # @return Returns a {@link #PageObject PageObject} instance. + def getPage(self, pageNumber): pages = self.getObject(self._pages) - pages["/Kids"].append(page) - pages[NameObject("/Count")] = NumberObject(pages["/Count"] + 1) + # XXX: crude hack + return pages["/Kids"][pageNumber].getObject() + + ## + # Return the number of pages. + # @return The number of pages. + def getNumPages(self): + pages = self.getObject(self._pages) + return int(pages[NameObject("/Count")]) + + ## + # Append a blank page to this PDF file and returns it. If no page size + # is specified, use the size of the last page; throw + # PageSizeNotDefinedError if it doesn't exist. + # @param width The width of the new page expressed in default user + # space units. + # @param height The height of the new page expressed in default user + # space units. + def addBlankPage(self, width=None, height=None): + page = PageObject.createBlankPage(self, width, height) + self.addPage(page) + return page + + ## + # Insert a blank page to this PDF file and returns it. If no page size + # is specified, use the size of the page in the given index; throw + # PageSizeNotDefinedError if it doesn't exist. + # @param width The width of the new page expressed in default user + # space units. + # @param height The height of the new page expressed in default user + # space units. + # @param index Position to add the page. + def insertBlankPage(self, width=None, height=None, index=0): + if width is None or height is None and \ + (self.getNumPages() - 1) >= index: + oldpage = self.getPage(index) + width = oldpage.mediaBox.getWidth() + height = oldpage.mediaBox.getHeight() + page = PageObject.createBlankPage(self, width, height) + self.insertPage(page, index) + return page ## # Encrypt this PDF file with the PDF Standard encryption handler. @@ -119,7 +197,7 @@ class PdfFileWriter(object): # encryption. When false, 40bit encryption will be used. By default, this # flag is on. def encrypt(self, user_pwd, owner_pwd = None, use_128bit = True): - import md5, time, random + import time, random if owner_pwd == None: owner_pwd = user_pwd if use_128bit: @@ -133,8 +211,8 @@ class PdfFileWriter(object): # permit everything: P = -1 O = ByteStringObject(_alg33(owner_pwd, user_pwd, rev, keylen)) - ID_1 = md5.new(repr(time.time())).digest() - ID_2 = md5.new(repr(random.random())).digest() + ID_1 = md5(repr(time.time())).digest() + ID_2 = md5(repr(random.random())).digest() self._ID = ArrayObject((ByteStringObject(ID_1), ByteStringObject(ID_2))) if rev == 2: U, key = _alg34(user_pwd, O, P, ID_1) @@ -160,9 +238,28 @@ class PdfFileWriter(object): # @param stream An object to write the file to. The object must support # the write method, and the tell method, similar to a file object. def write(self, stream): - import struct, md5 + import struct externalReferenceMap = {} + + # PDF objects sometimes have circular references to their /Page objects + # inside their object tree (for example, annotations). Those will be + # indirect references to objects that we've recreated in this PDF. To + # address this problem, PageObject's store their original object + # reference number, and we add it to the external reference map before + # we sweep for indirect references. This forces self-page-referencing + # trees to reference the correct new object location, rather than + # copying in a new copy of the page object. + for objIndex in xrange(len(self._objects)): + obj = self._objects[objIndex] + if isinstance(obj, PageObject) and obj.indirectRef != None: + data = obj.indirectRef + if not externalReferenceMap.has_key(data.pdf): + externalReferenceMap[data.pdf] = {} + if not externalReferenceMap[data.pdf].has_key(data.generation): + externalReferenceMap[data.pdf][data.generation] = {} + externalReferenceMap[data.pdf][data.generation][data.idnum] = IndirectObject(objIndex + 1, 0, self) + self.stack = [] self._sweepIndirectReferences(externalReferenceMap, self._root) del self.stack @@ -181,7 +278,7 @@ class PdfFileWriter(object): pack2 = struct.pack(" 0: + lastpage = pdf.getPage(pdf.getNumPages() - 1) + width = lastpage.mediaBox.getWidth() + height = lastpage.mediaBox.getHeight() + else: + raise utils.PageSizeNotDefinedError() + page.__setitem__(NameObject('/MediaBox'), + RectangleObject([0, 0, width, height])) + + return page + createBlankPage = staticmethod(createBlankPage) ## # Rotates a page clockwise by increments of 90 degrees. @@ -931,7 +1066,7 @@ class PageObject(DictionaryObject): renameRes[key] = newname newRes[newname] = page2Res[key] elif not newRes.has_key(key): - newRes[key] = page2Res[key] + newRes[key] = page2Res.raw_get(key) return newRes, renameRes _mergeResources = staticmethod(_mergeResources) @@ -957,6 +1092,26 @@ class PageObject(DictionaryObject): return stream _pushPopGS = staticmethod(_pushPopGS) + def _addTransformationMatrix(contents, pdf, ctm): + # adds transformation matrix at the beginning of the given + # contents stream. + a, b, c, d, e, f = ctm + contents = ContentStream(contents, pdf) + contents.operations.insert(0, [[FloatObject(a), FloatObject(b), + FloatObject(c), FloatObject(d), FloatObject(e), + FloatObject(f)], " cm"]) + return contents + _addTransformationMatrix = staticmethod(_addTransformationMatrix) + + ## + # Returns the /Contents object, or None if it doesn't exist. + # /Contents is optionnal, as described in PDF Reference 7.7.3.3 + def getContents(self): + if self.has_key("/Contents"): + return self["/Contents"].getObject() + else: + return None + ## # Merges the content streams of two pages into one. Resource references # (i.e. fonts) are maintained from both pages. The mediabox/cropbox/etc @@ -968,7 +1123,23 @@ class PageObject(DictionaryObject): # @param page2 An instance of {@link #PageObject PageObject} to be merged # into this one. def mergePage(self, page2): + self._mergePage(page2) + ## + # Actually merges the content streams of two pages into one. Resource + # references (i.e. fonts) are maintained from both pages. The + # mediabox/cropbox/etc of this page are not altered. The parameter page's + # content stream will be added to the end of this page's content stream, + # meaning that it will be drawn after, or "on top" of this page. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged + # into this one. + # @param page2transformation A fuction which applies a transformation to + # the content stream of page2. Takes: page2 + # contents stream. Must return: new contents + # stream. If omitted, the content stream will + # not be modified. + def _mergePage(self, page2, page2transformation=None): # First we work on merging the resource dictionaries. This allows us # to find out what symbols in the content streams we might need to # rename. @@ -978,7 +1149,7 @@ class PageObject(DictionaryObject): originalResources = self["/Resources"].getObject() page2Resources = page2["/Resources"].getObject() - for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading": + for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading", "/Properties": new, newrename = PageObject._mergeResources(originalResources, page2Resources, res) if new: newResources[NameObject(res)] = new @@ -993,17 +1164,191 @@ class PageObject(DictionaryObject): newContentArray = ArrayObject() - originalContent = self["/Contents"].getObject() - newContentArray.append(PageObject._pushPopGS(originalContent, self.pdf)) + originalContent = self.getContents() + if originalContent is not None: + newContentArray.append(PageObject._pushPopGS( + originalContent, self.pdf)) - page2Content = page2['/Contents'].getObject() - page2Content = PageObject._contentStreamRename(page2Content, rename, self.pdf) - page2Content = PageObject._pushPopGS(page2Content, self.pdf) - newContentArray.append(page2Content) + page2Content = page2.getContents() + if page2Content is not None: + if page2transformation is not None: + page2Content = page2transformation(page2Content) + page2Content = PageObject._contentStreamRename( + page2Content, rename, self.pdf) + page2Content = PageObject._pushPopGS(page2Content, self.pdf) + newContentArray.append(page2Content) self[NameObject('/Contents')] = ContentStream(newContentArray, self.pdf) self[NameObject('/Resources')] = newResources + ## + # This is similar to mergePage, but a transformation matrix is + # applied to the merged stream. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param ctm A 6 elements tuple containing the operands of the + # transformation matrix + def mergeTransformedPage(self, page2, ctm): + self._mergePage(page2, lambda page2Content: + PageObject._addTransformationMatrix(page2Content, page2.pdf, ctm)) + + ## + # This is similar to mergePage, but the stream to be merged is scaled + # by appling a transformation matrix. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param factor The scaling factor + def mergeScaledPage(self, page2, factor): + # CTM to scale : [ sx 0 0 sy 0 0 ] + return self.mergeTransformedPage(page2, [factor, 0, + 0, factor, + 0, 0]) + + ## + # This is similar to mergePage, but the stream to be merged is rotated + # by appling a transformation matrix. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param rotation The angle of the rotation, in degrees + def mergeRotatedPage(self, page2, rotation): + rotation = math.radians(rotation) + return self.mergeTransformedPage(page2, + [math.cos(rotation), math.sin(rotation), + -math.sin(rotation), math.cos(rotation), + 0, 0]) + + ## + # This is similar to mergePage, but the stream to be merged is translated + # by appling a transformation matrix. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param tx The translation on X axis + # @param tx The translation on Y axis + def mergeTranslatedPage(self, page2, tx, ty): + return self.mergeTransformedPage(page2, [1, 0, + 0, 1, + tx, ty]) + + ## + # This is similar to mergePage, but the stream to be merged is rotated + # and scaled by appling a transformation matrix. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param rotation The angle of the rotation, in degrees + # @param factor The scaling factor + def mergeRotatedScaledPage(self, page2, rotation, scale): + rotation = math.radians(rotation) + rotating = [[math.cos(rotation), math.sin(rotation),0], + [-math.sin(rotation),math.cos(rotation), 0], + [0, 0, 1]] + scaling = [[scale,0, 0], + [0, scale,0], + [0, 0, 1]] + ctm = utils.matrixMultiply(rotating, scaling) + + return self.mergeTransformedPage(page2, + [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]]) + + ## + # This is similar to mergePage, but the stream to be merged is translated + # and scaled by appling a transformation matrix. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param scale The scaling factor + # @param tx The translation on X axis + # @param tx The translation on Y axis + def mergeScaledTranslatedPage(self, page2, scale, tx, ty): + translation = [[1, 0, 0], + [0, 1, 0], + [tx,ty,1]] + scaling = [[scale,0, 0], + [0, scale,0], + [0, 0, 1]] + ctm = utils.matrixMultiply(scaling, translation) + + return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]]) + + ## + # This is similar to mergePage, but the stream to be merged is translated, + # rotated and scaled by appling a transformation matrix. + # + # @param page2 An instance of {@link #PageObject PageObject} to be merged. + # @param tx The translation on X axis + # @param ty The translation on Y axis + # @param rotation The angle of the rotation, in degrees + # @param scale The scaling factor + def mergeRotatedScaledTranslatedPage(self, page2, rotation, scale, tx, ty): + translation = [[1, 0, 0], + [0, 1, 0], + [tx,ty,1]] + rotation = math.radians(rotation) + rotating = [[math.cos(rotation), math.sin(rotation),0], + [-math.sin(rotation),math.cos(rotation), 0], + [0, 0, 1]] + scaling = [[scale,0, 0], + [0, scale,0], + [0, 0, 1]] + ctm = utils.matrixMultiply(rotating, scaling) + ctm = utils.matrixMultiply(ctm, translation) + + return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1], + ctm[1][0], ctm[1][1], + ctm[2][0], ctm[2][1]]) + + ## + # Applys a transformation matrix the page. + # + # @param ctm A 6 elements tuple containing the operands of the + # transformation matrix + def addTransformation(self, ctm): + originalContent = self.getContents() + if originalContent is not None: + newContent = PageObject._addTransformationMatrix( + originalContent, self.pdf, ctm) + newContent = PageObject._pushPopGS(newContent, self.pdf) + self[NameObject('/Contents')] = newContent + + ## + # Scales a page by the given factors by appling a transformation + # matrix to its content and updating the page size. + # + # @param sx The scaling factor on horizontal axis + # @param sy The scaling factor on vertical axis + def scale(self, sx, sy): + self.addTransformation([sx, 0, + 0, sy, + 0, 0]) + self.mediaBox = RectangleObject([ + float(self.mediaBox.getLowerLeft_x()) * sx, + float(self.mediaBox.getLowerLeft_y()) * sy, + float(self.mediaBox.getUpperRight_x()) * sx, + float(self.mediaBox.getUpperRight_y()) * sy]) + + ## + # Scales a page by the given factor by appling a transformation + # matrix to its content and updating the page size. + # + # @param factor The scaling factor + def scaleBy(self, factor): + self.scale(factor, factor) + + ## + # Scales a page to the specified dimentions by appling a + # transformation matrix to its content and updating the page size. + # + # @param width The new width + # @param height The new heigth + def scaleTo(self, width, height): + sx = width / (self.mediaBox.getUpperRight_x() - + self.mediaBox.getLowerLeft_x ()) + sy = height / (self.mediaBox.getUpperRight_y() - + self.mediaBox.getLowerLeft_x ()) + self.scale(sx, sy) + ## # Compresses the size of this page by joining all content streams and # applying a FlateDecode filter. @@ -1012,10 +1357,11 @@ class PageObject(DictionaryObject): # However, it is possible that this function will perform no action if # content stream compression becomes "automatic" for some reason. def compressContentStreams(self): - content = self["/Contents"].getObject() - if not isinstance(content, ContentStream): - content = ContentStream(content, self.pdf) - self[NameObject("/Contents")] = content.flateEncode() + content = self.getContents() + if content is not None: + if not isinstance(content, ContentStream): + content = ContentStream(content, self.pdf) + self[NameObject("/Contents")] = content.flateEncode() ## # Locate all text drawing commands, in the order they are provided in the @@ -1369,8 +1715,8 @@ def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr password = (password + _encryption_padding)[:32] # 2. Initialize the MD5 hash function and pass the result of step 1 as # input to this function. - import md5, struct - m = md5.new(password) + import struct + m = md5(password) # 3. Pass the value of the encryption dictionary's /O entry to the MD5 hash # function. m.update(owner_entry) @@ -1394,7 +1740,7 @@ def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr # /Length entry. if rev >= 3: for i in range(50): - md5_hash = md5.new(md5_hash[:keylen]).digest() + md5_hash = md5(md5_hash[:keylen]).digest() # 9. Set the encryption key to the first n bytes of the output from the # final MD5 hash, where n is always 5 for revision 2 but, for revision 3 or # greater, depends on the value of the encryption dictionary's /Length @@ -1436,14 +1782,13 @@ def _alg33_1(password, rev, keylen): password = (password + _encryption_padding)[:32] # 2. Initialize the MD5 hash function and pass the result of step 1 as # input to this function. - import md5 - m = md5.new(password) + m = md5(password) # 3. (Revision 3 or greater) Do the following 50 times: Take the output # from the previous MD5 hash and pass it as input into a new MD5 hash. md5_hash = m.digest() if rev >= 3: for i in range(50): - md5_hash = md5.new(md5_hash).digest() + md5_hash = md5(md5_hash).digest() # 4. Create an RC4 encryption key using the first n bytes of the output # from the final MD5 hash, where n is always 5 for revision 2 but, for # revision 3 or greater, depends on the value of the encryption @@ -1473,8 +1818,7 @@ def _alg35(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr key = _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry) # 2. Initialize the MD5 hash function and pass the 32-byte padding string # shown in step 1 of Algorithm 3.2 as input to this function. - import md5 - m = md5.new() + m = md5() m.update(_encryption_padding) # 3. Pass the first element of the file's file identifier array (the value # of the ID entry in the document's trailer dictionary; see Table 3.13 on diff --git a/bin/report/pyPdf/utils.py b/bin/report/pyPdf/utils.py index 2a778dd0c05..7c1d472d0b7 100644 --- a/bin/report/pyPdf/utils.py +++ b/bin/report/pyPdf/utils.py @@ -1,111 +1,122 @@ -# -*- coding: utf-8 -*- -# vim: sw=4:expandtab:foldmethod=marker -# -# Copyright (c) 2006, Mathieu Fenniak -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - - -""" -Utility functions for PDF library. -""" -__author__ = "Mathieu Fenniak" -__author_email__ = "biziqe@mathieu.fenniak.net" - -#ENABLE_PSYCO = False -#if ENABLE_PSYCO: -# try: -# import psyco -# except ImportError: -# ENABLE_PSYCO = False -# -#if not ENABLE_PSYCO: -# class psyco: -# def proxy(func): -# return func -# proxy = staticmethod(proxy) - -def readUntilWhitespace(stream, maxchars=None): - txt = "" - while True: - tok = stream.read(1) - if tok.isspace() or not tok: - break - txt += tok - if len(txt) == maxchars: - break - return txt - -def readNonWhitespace(stream): - tok = ' ' - while tok == '\n' or tok == '\r' or tok == ' ' or tok == '\t': - tok = stream.read(1) - return tok - -class ConvertFunctionsToVirtualList(object): - def __init__(self, lengthFunction, getFunction): - self.lengthFunction = lengthFunction - self.getFunction = getFunction - - def __len__(self): - return self.lengthFunction() - - def __getitem__(self, index): - if not isinstance(index, int): - raise TypeError, "sequence indices must be integers" - len_self = len(self) - if index < 0: - # support negative indexes - index = len_self + index - if index < 0 or index >= len_self: - raise IndexError, "sequence index out of range" - return self.getFunction(index) - -def RC4_encrypt(key, plaintext): - S = [i for i in range(256)] - j = 0 - for i in range(256): - j = (j + S[i] + ord(key[i % len(key)])) % 256 - S[i], S[j] = S[j], S[i] - i, j = 0, 0 - retval = "" - for x in range(len(plaintext)): - i = (i + 1) % 256 - j = (j + S[i]) % 256 - S[i], S[j] = S[j], S[i] - t = S[(S[i] + S[j]) % 256] - retval += chr(ord(plaintext[x]) ^ t) - return retval - -class PdfReadError(Exception): - pass - -if __name__ == "__main__": - # test RC4 - out = RC4_encrypt("Key", "Plaintext") - print repr(out) - pt = RC4_encrypt("Key", out) - print repr(pt) +# vim: sw=4:expandtab:foldmethod=marker +# +# Copyright (c) 2006, Mathieu Fenniak +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +""" +Utility functions for PDF library. +""" +__author__ = "Mathieu Fenniak" +__author_email__ = "biziqe@mathieu.fenniak.net" + +#ENABLE_PSYCO = False +#if ENABLE_PSYCO: +# try: +# import psyco +# except ImportError: +# ENABLE_PSYCO = False +# +#if not ENABLE_PSYCO: +# class psyco: +# def proxy(func): +# return func +# proxy = staticmethod(proxy) + +def readUntilWhitespace(stream, maxchars=None): + txt = "" + while True: + tok = stream.read(1) + if tok.isspace() or not tok: + break + txt += tok + if len(txt) == maxchars: + break + return txt + +def readNonWhitespace(stream): + tok = ' ' + while tok == '\n' or tok == '\r' or tok == ' ' or tok == '\t': + tok = stream.read(1) + return tok + +class ConvertFunctionsToVirtualList(object): + def __init__(self, lengthFunction, getFunction): + self.lengthFunction = lengthFunction + self.getFunction = getFunction + + def __len__(self): + return self.lengthFunction() + + def __getitem__(self, index): + if not isinstance(index, int): + raise TypeError, "sequence indices must be integers" + len_self = len(self) + if index < 0: + # support negative indexes + index = len_self + index + if index < 0 or index >= len_self: + raise IndexError, "sequence index out of range" + return self.getFunction(index) + +def RC4_encrypt(key, plaintext): + S = [i for i in range(256)] + j = 0 + for i in range(256): + j = (j + S[i] + ord(key[i % len(key)])) % 256 + S[i], S[j] = S[j], S[i] + i, j = 0, 0 + retval = "" + for x in range(len(plaintext)): + i = (i + 1) % 256 + j = (j + S[i]) % 256 + S[i], S[j] = S[j], S[i] + t = S[(S[i] + S[j]) % 256] + retval += chr(ord(plaintext[x]) ^ t) + return retval + +def matrixMultiply(a, b): + return [[sum([float(i)*float(j) + for i, j in zip(row, col)] + ) for col in zip(*b)] + for row in a] + +class PyPdfError(Exception): + pass + +class PdfReadError(PyPdfError): + pass + +class PageSizeNotDefinedError(PyPdfError): + pass + +if __name__ == "__main__": + # test RC4 + out = RC4_encrypt("Key", "Plaintext") + print repr(out) + pt = RC4_encrypt("Key", out) + print repr(pt) diff --git a/bin/report/pyPdf/xmp.py b/bin/report/pyPdf/xmp.py index 5018b7825ed..b070df9093e 100644 --- a/bin/report/pyPdf/xmp.py +++ b/bin/report/pyPdf/xmp.py @@ -1,356 +1,355 @@ -# -*- coding: utf-8 -*- -import re -import datetime -import decimal -from generic import PdfObject -from xml.dom import getDOMImplementation -from xml.dom.minidom import parseString - -RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" -DC_NAMESPACE = "http://purl.org/dc/elements/1.1/" -XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/" -PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/" -XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/" - -# What is the PDFX namespace, you might ask? I might ask that too. It's -# a completely undocumented namespace used to place "custom metadata" -# properties, which are arbitrary metadata properties with no semantic or -# documented meaning. Elements in the namespace are key/value-style storage, -# where the element name is the key and the content is the value. The keys -# are transformed into valid XML identifiers by substituting an invalid -# identifier character with \u2182 followed by the unicode hex ID of the -# original character. A key like "my car" is therefore "my\u21820020car". -# -# \u2182, in case you're wondering, is the unicode character -# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for -# escaping characters. -# -# Intentional users of the pdfx namespace should be shot on sight. A -# custom data schema and sensical XML elements could be used instead, as is -# suggested by Adobe's own documentation on XMP (under "Extensibility of -# Schemas"). -# -# Information presented here on the /pdfx/ schema is a result of limited -# reverse engineering, and does not constitute a full specification. -PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/" - -iso8601 = re.compile(""" - (?P[0-9]{4}) - (- - (?P[0-9]{2}) - (- - (?P[0-9]+) - (T - (?P[0-9]{2}): - (?P[0-9]{2}) - (:(?P[0-9]{2}(.[0-9]+)?))? - (?PZ|[-+][0-9]{2}:[0-9]{2}) - )? - )? - )? - """, re.VERBOSE) - -## -# An object that represents Adobe XMP metadata. -class XmpInformation(PdfObject): - - def __init__(self, stream): - self.stream = stream - docRoot = parseString(self.stream.getData()) - self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0] - self.cache = {} - - def writeToStream(self, stream, encryption_key): - self.stream.writeToStream(stream, encryption_key) - - def getElement(self, aboutUri, namespace, name): - for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"): - if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri: - attr = desc.getAttributeNodeNS(namespace, name) - if attr != None: - yield attr - for element in desc.getElementsByTagNameNS(namespace, name): - yield element - - def getNodesInNamespace(self, aboutUri, namespace): - for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"): - if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri: - for i in range(desc.attributes.length): - attr = desc.attributes.item(i) - if attr.namespaceURI == namespace: - yield attr - for child in desc.childNodes: - if child.namespaceURI == namespace: - yield child - - def _getText(self, element): - text = "" - for child in element.childNodes: - if child.nodeType == child.TEXT_NODE: - text += child.data - return text - - def _converter_string(value): - return value - - def _converter_date(value): - m = iso8601.match(value) - year = int(m.group("year")) - month = int(m.group("month") or "1") - day = int(m.group("day") or "1") - hour = int(m.group("hour") or "0") - minute = int(m.group("minute") or "0") - second = decimal.Decimal(m.group("second") or "0") - seconds = second.to_integral(decimal.ROUND_FLOOR) - milliseconds = (second - seconds) * 1000000 - tzd = m.group("tzd") or "Z" - dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds) - if tzd != "Z": - tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")] - tzd_hours *= -1 - if tzd_hours < 0: - tzd_minutes *= -1 - dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes) - return dt - _test_converter_date = staticmethod(_converter_date) - - def _getter_bag(namespace, name, converter): - def get(self): - cached = self.cache.get(namespace, {}).get(name) - if cached: - return cached - retval = [] - for element in self.getElement("", namespace, name): - bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag") - if len(bags): - for bag in bags: - for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"): - value = self._getText(item) - value = converter(value) - retval.append(value) - ns_cache = self.cache.setdefault(namespace, {}) - ns_cache[name] = retval - return retval - return get - - def _getter_seq(namespace, name, converter): - def get(self): - cached = self.cache.get(namespace, {}).get(name) - if cached: - return cached - retval = [] - for element in self.getElement("", namespace, name): - seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq") - if len(seqs): - for seq in seqs: - for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"): - value = self._getText(item) - value = converter(value) - retval.append(value) - else: - value = converter(self._getText(element)) - retval.append(value) - ns_cache = self.cache.setdefault(namespace, {}) - ns_cache[name] = retval - return retval - return get - - def _getter_langalt(namespace, name, converter): - def get(self): - cached = self.cache.get(namespace, {}).get(name) - if cached: - return cached - retval = {} - for element in self.getElement("", namespace, name): - alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt") - if len(alts): - for alt in alts: - for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"): - value = self._getText(item) - value = converter(value) - retval[item.getAttribute("xml:lang")] = value - else: - retval["x-default"] = converter(self._getText(element)) - ns_cache = self.cache.setdefault(namespace, {}) - ns_cache[name] = retval - return retval - return get - - def _getter_single(namespace, name, converter): - def get(self): - cached = self.cache.get(namespace, {}).get(name) - if cached: - return cached - value = None - for element in self.getElement("", namespace, name): - if element.nodeType == element.ATTRIBUTE_NODE: - value = element.nodeValue - else: - value = self._getText(element) - break - if value != None: - value = converter(value) - ns_cache = self.cache.setdefault(namespace, {}) - ns_cache[name] = value - return value - return get - - ## - # Contributors to the resource (other than the authors). An unsorted - # array of names. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string)) - - ## - # Text describing the extent or scope of the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string)) - - ## - # A sorted array of names of the authors of the resource, listed in order - # of precedence. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string)) - - ## - # A sorted array of dates (datetime.datetime instances) of signifigance to - # the resource. The dates and times are in UTC. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date)) - - ## - # A language-keyed dictionary of textual descriptions of the content of the - # resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string)) - - ## - # The mime-type of the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string)) - - ## - # Unique identifier of the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string)) - - ## - # An unordered array specifying the languages used in the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string)) - - ## - # An unordered array of publisher names. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string)) - - ## - # An unordered array of text descriptions of relationships to other - # documents. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string)) - - ## - # A language-keyed dictionary of textual descriptions of the rights the - # user has to this resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string)) - - ## - # Unique identifier of the work from which this resource was derived. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string)) - - ## - # An unordered array of descriptive phrases or keywrods that specify the - # topic of the content of the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string)) - - ## - # A language-keyed dictionary of the title of the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string)) - - ## - # An unordered array of textual descriptions of the document type. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string)) - - ## - # An unformatted text string representing document keywords. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string)) - - ## - # The PDF file version, for example 1.0, 1.3. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string)) - - ## - # The name of the tool that created the PDF document. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string)) - - ## - # The date and time the resource was originally created. The date and - # time are returned as a UTC datetime.datetime object. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date)) - - ## - # The date and time the resource was last modified. The date and time - # are returned as a UTC datetime.datetime object. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date)) - - ## - # The date and time that any metadata for this resource was last - # changed. The date and time are returned as a UTC datetime.datetime - # object. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date)) - - ## - # The name of the first known tool used to create the resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string)) - - ## - # The common identifier for all versions and renditions of this resource. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string)) - - ## - # An identifier for a specific incarnation of a document, updated each - # time a file is saved. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string)) - - def custom_properties(self): - if not hasattr(self, "_custom_properties"): - self._custom_properties = {} - for node in self.getNodesInNamespace("", PDFX_NAMESPACE): - key = node.localName - while True: - # see documentation about PDFX_NAMESPACE earlier in file - idx = key.find(u"\u2182") - if idx == -1: - break - key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:] - if node.nodeType == node.ATTRIBUTE_NODE: - value = node.nodeValue - else: - value = self._getText(node) - self._custom_properties[key] = value - return self._custom_properties - - ## - # Retrieves custom metadata properties defined in the undocumented pdfx - # metadata schema. - #

Stability: Added in v1.12, will exist for all future v1.x releases. - # @return Returns a dictionary of key/value items for custom metadata - # properties. - custom_properties = property(custom_properties) - - +import re +import datetime +import decimal +from generic import PdfObject +from xml.dom import getDOMImplementation +from xml.dom.minidom import parseString + +RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +DC_NAMESPACE = "http://purl.org/dc/elements/1.1/" +XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/" +PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/" +XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/" + +# What is the PDFX namespace, you might ask? I might ask that too. It's +# a completely undocumented namespace used to place "custom metadata" +# properties, which are arbitrary metadata properties with no semantic or +# documented meaning. Elements in the namespace are key/value-style storage, +# where the element name is the key and the content is the value. The keys +# are transformed into valid XML identifiers by substituting an invalid +# identifier character with \u2182 followed by the unicode hex ID of the +# original character. A key like "my car" is therefore "my\u21820020car". +# +# \u2182, in case you're wondering, is the unicode character +# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for +# escaping characters. +# +# Intentional users of the pdfx namespace should be shot on sight. A +# custom data schema and sensical XML elements could be used instead, as is +# suggested by Adobe's own documentation on XMP (under "Extensibility of +# Schemas"). +# +# Information presented here on the /pdfx/ schema is a result of limited +# reverse engineering, and does not constitute a full specification. +PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/" + +iso8601 = re.compile(""" + (?P[0-9]{4}) + (- + (?P[0-9]{2}) + (- + (?P[0-9]+) + (T + (?P[0-9]{2}): + (?P[0-9]{2}) + (:(?P[0-9]{2}(.[0-9]+)?))? + (?PZ|[-+][0-9]{2}:[0-9]{2}) + )? + )? + )? + """, re.VERBOSE) + +## +# An object that represents Adobe XMP metadata. +class XmpInformation(PdfObject): + + def __init__(self, stream): + self.stream = stream + docRoot = parseString(self.stream.getData()) + self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0] + self.cache = {} + + def writeToStream(self, stream, encryption_key): + self.stream.writeToStream(stream, encryption_key) + + def getElement(self, aboutUri, namespace, name): + for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"): + if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri: + attr = desc.getAttributeNodeNS(namespace, name) + if attr != None: + yield attr + for element in desc.getElementsByTagNameNS(namespace, name): + yield element + + def getNodesInNamespace(self, aboutUri, namespace): + for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"): + if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri: + for i in range(desc.attributes.length): + attr = desc.attributes.item(i) + if attr.namespaceURI == namespace: + yield attr + for child in desc.childNodes: + if child.namespaceURI == namespace: + yield child + + def _getText(self, element): + text = "" + for child in element.childNodes: + if child.nodeType == child.TEXT_NODE: + text += child.data + return text + + def _converter_string(value): + return value + + def _converter_date(value): + m = iso8601.match(value) + year = int(m.group("year")) + month = int(m.group("month") or "1") + day = int(m.group("day") or "1") + hour = int(m.group("hour") or "0") + minute = int(m.group("minute") or "0") + second = decimal.Decimal(m.group("second") or "0") + seconds = second.to_integral(decimal.ROUND_FLOOR) + milliseconds = (second - seconds) * 1000000 + tzd = m.group("tzd") or "Z" + dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds) + if tzd != "Z": + tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")] + tzd_hours *= -1 + if tzd_hours < 0: + tzd_minutes *= -1 + dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes) + return dt + _test_converter_date = staticmethod(_converter_date) + + def _getter_bag(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + retval = [] + for element in self.getElement("", namespace, name): + bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag") + if len(bags): + for bag in bags: + for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"): + value = self._getText(item) + value = converter(value) + retval.append(value) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = retval + return retval + return get + + def _getter_seq(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + retval = [] + for element in self.getElement("", namespace, name): + seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq") + if len(seqs): + for seq in seqs: + for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"): + value = self._getText(item) + value = converter(value) + retval.append(value) + else: + value = converter(self._getText(element)) + retval.append(value) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = retval + return retval + return get + + def _getter_langalt(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + retval = {} + for element in self.getElement("", namespace, name): + alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt") + if len(alts): + for alt in alts: + for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"): + value = self._getText(item) + value = converter(value) + retval[item.getAttribute("xml:lang")] = value + else: + retval["x-default"] = converter(self._getText(element)) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = retval + return retval + return get + + def _getter_single(namespace, name, converter): + def get(self): + cached = self.cache.get(namespace, {}).get(name) + if cached: + return cached + value = None + for element in self.getElement("", namespace, name): + if element.nodeType == element.ATTRIBUTE_NODE: + value = element.nodeValue + else: + value = self._getText(element) + break + if value != None: + value = converter(value) + ns_cache = self.cache.setdefault(namespace, {}) + ns_cache[name] = value + return value + return get + + ## + # Contributors to the resource (other than the authors). An unsorted + # array of names. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string)) + + ## + # Text describing the extent or scope of the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string)) + + ## + # A sorted array of names of the authors of the resource, listed in order + # of precedence. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string)) + + ## + # A sorted array of dates (datetime.datetime instances) of signifigance to + # the resource. The dates and times are in UTC. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date)) + + ## + # A language-keyed dictionary of textual descriptions of the content of the + # resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string)) + + ## + # The mime-type of the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string)) + + ## + # Unique identifier of the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string)) + + ## + # An unordered array specifying the languages used in the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string)) + + ## + # An unordered array of publisher names. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string)) + + ## + # An unordered array of text descriptions of relationships to other + # documents. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string)) + + ## + # A language-keyed dictionary of textual descriptions of the rights the + # user has to this resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string)) + + ## + # Unique identifier of the work from which this resource was derived. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string)) + + ## + # An unordered array of descriptive phrases or keywrods that specify the + # topic of the content of the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string)) + + ## + # A language-keyed dictionary of the title of the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string)) + + ## + # An unordered array of textual descriptions of the document type. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string)) + + ## + # An unformatted text string representing document keywords. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string)) + + ## + # The PDF file version, for example 1.0, 1.3. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string)) + + ## + # The name of the tool that created the PDF document. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string)) + + ## + # The date and time the resource was originally created. The date and + # time are returned as a UTC datetime.datetime object. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date)) + + ## + # The date and time the resource was last modified. The date and time + # are returned as a UTC datetime.datetime object. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date)) + + ## + # The date and time that any metadata for this resource was last + # changed. The date and time are returned as a UTC datetime.datetime + # object. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date)) + + ## + # The name of the first known tool used to create the resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string)) + + ## + # The common identifier for all versions and renditions of this resource. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string)) + + ## + # An identifier for a specific incarnation of a document, updated each + # time a file is saved. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string)) + + def custom_properties(self): + if not hasattr(self, "_custom_properties"): + self._custom_properties = {} + for node in self.getNodesInNamespace("", PDFX_NAMESPACE): + key = node.localName + while True: + # see documentation about PDFX_NAMESPACE earlier in file + idx = key.find(u"\u2182") + if idx == -1: + break + key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:] + if node.nodeType == node.ATTRIBUTE_NODE: + value = node.nodeValue + else: + value = self._getText(node) + self._custom_properties[key] = value + return self._custom_properties + + ## + # Retrieves custom metadata properties defined in the undocumented pdfx + # metadata schema. + #

Stability: Added in v1.12, will exist for all future v1.x releases. + # @return Returns a dictionary of key/value items for custom metadata + # properties. + custom_properties = property(custom_properties) + + From 438232f6bef8e0b2d1a089dddf0c91685e1da001 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Mon, 10 Jan 2011 16:19:12 +0100 Subject: [PATCH 03/50] [FIX] res_lang: sanitize time_format for python's strftime POSIX's strftime has a number of 'compound' formats for quick-formatting of time spans (e.g. %T for %H:%M:%S) which Python's own strftime does not support => error when trying to use them manually expand those compound formats to their atoms refs: http://pubs.opengroup.org/onlinepubs/009695399/functions/strftime.html http://docs.python.org/library/time.html#time.strftime lp bug: https://launchpad.net/bugs/698134 fixed lp bug: https://launchpad.net/bugs/697833 fixed bzr revid: odo@openerp.com-20110110151912-ysu06271z40hxxho --- bin/addons/base/res/res_lang.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/addons/base/res/res_lang.py b/bin/addons/base/res/res_lang.py index abaa81ed357..d19e9389884 100644 --- a/bin/addons/base/res/res_lang.py +++ b/bin/addons/base/res/res_lang.py @@ -66,17 +66,29 @@ class lang(osv.osv): def fix_xa0(s): + """Fix badly-encoded non-breaking space Unicode character from locale.localeconv(), + coercing to utf-8, as some platform seem to output localeconv() in their system + encoding, e.g. Windows-1252""" if s == '\xa0': return '\xc2\xa0' return s + def fix_time_format(format): + """Python's strftime does not support all the compound formats + from C's strftime, as returned by locale.nl_langinfo(). + Under Ubuntu at least, we encounter %T or %r for some locales.""" + format = format.replace('%T', '%H:%M:%S')\ + .replace('%r', '%I:%M:%S %p')\ + .replace('%R', '%H:%M') + return str(format) + lang_info = { 'code': lang, 'iso_code': iso_lang, 'name': lang_name, 'translatable': 1, 'date_format' : str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')), - 'time_format' : str(locale.nl_langinfo(locale.T_FMT)), + 'time_format' : fix_time_format(locale.nl_langinfo(locale.T_FMT)), 'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])), 'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])), } From 849e49a2b4a5882c7cdb81558d03897e8fdaf289 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Mon, 10 Jan 2011 18:03:28 +0100 Subject: [PATCH 04/50] [IMP] index on partner's ref bzr revid: fp@tinyerp.com-20110110170328-85g072pod3v2x3d9 --- bin/addons/base/res/ir_property.py | 8 +++++++- bin/addons/base/res/partner/partner.py | 4 ++-- bin/osv/fields.py | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bin/addons/base/res/ir_property.py b/bin/addons/base/res/ir_property.py index 43451f824a1..6ccbf06187e 100644 --- a/bin/addons/base/res/ir_property.py +++ b/bin/addons/base/res/ir_property.py @@ -136,6 +136,7 @@ class ir_property(osv.osv): return super(ir_property, self).create(cr, uid, self._update_values(cr, uid, None, values), context=context) def get_by_record(self, cr, uid, record, context=None): + print 'record type', record.type if record.type in ('char', 'text'): return record.value_text elif record.type == 'float': @@ -157,13 +158,18 @@ class ir_property(osv.osv): return False def get(self, cr, uid, name, model, res_id=False, context={}): + print 'Property Get', name, model domain = self._get_domain(cr, uid, name, model, context=context) if domain is not None: domain = [('res_id', '=', res_id)] + domain nid = self.search(cr, uid, domain, context=context) + print 'DOMAIN', domain, nid if not nid: return False record = self.browse(cr, uid, nid[0], context=context) - return self.get_by_record(cr, uid, record, context=context) + print 'RECORD', record + res = self.get_by_record(cr, uid, record, context=context) + print 'Result', res + return res return False def _get_domain_default(self, cr, uid, prop_name, model, context=None): diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py index 8a6a6197ab3..d7f5b11a71b 100644 --- a/bin/addons/base/res/partner/partner.py +++ b/bin/addons/base/res/partner/partner.py @@ -113,9 +113,9 @@ class res_partner(osv.osv): 'name': fields.char('Name', size=128, required=True, select=True), 'date': fields.date('Date', select=1), 'title': fields.many2one('res.partner.title','Partner Form'), - 'parent_id': fields.many2one('res.partner','Parent Partner', select=2), + 'parent_id': fields.many2one('res.partner','Parent Partner'), 'child_ids': fields.one2many('res.partner', 'parent_id', 'Partner Ref.'), - 'ref': fields.char('Reference', size=64), + 'ref': fields.char('Reference', size=64, select=1), 'lang': fields.selection(_lang_get, 'Language', size=5, help="If the selected language is loaded in the system, all documents related to this partner will be printed in this language. If not, it will be english."), 'user_id': fields.many2one('res.users', 'Salesman', help='The internal user that is in charge of communicating with this partner if any.'), 'vat': fields.char('VAT',size=32 ,help="Value Added Tax number. Check the box if the partner is subjected to the VAT. Used by the VAT legal statement."), diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 6f720ea4669..5173b7e29cf 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -983,6 +983,7 @@ class property(function): prop = obj.pool.get('ir.property') domain = [('fields_id.model', '=', obj._name), ('fields_id.name','in',prop_name), ('res_id','=',False)] ids = prop.search(cr, uid, domain, context=context) + print 'GOT IDS', ids, 'FOR', prop_name replaces = {} default_value = {}.fromkeys(prop_name, False) for prop_rec in prop.browse(cr, uid, ids, context=context): From db0f75705c99e2fa2f61ca125b6e3a891bb14203 Mon Sep 17 00:00:00 2001 From: "aag (OpenERP)" Date: Fri, 7 Jan 2011 17:45:07 +0530 Subject: [PATCH 07/50] [ADD] purchase_double_validation:Add yml for Double validation bzr revid: aag@tinyerp.co.in-20110107121507-yqcn0cusopw2fu57 --- .../purchase_double_validation/__openerp__.py | 3 + .../test/purchase_double_validation_test.yml | 117 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 addons/purchase_double_validation/test/purchase_double_validation_test.yml diff --git a/addons/purchase_double_validation/__openerp__.py b/addons/purchase_double_validation/__openerp__.py index ca21dfc6077..18f5b4a80b9 100644 --- a/addons/purchase_double_validation/__openerp__.py +++ b/addons/purchase_double_validation/__openerp__.py @@ -35,6 +35,9 @@ 'purchase_double_validation_installer.xml' ], 'demo_xml': [], + 'test': [ + 'test/purchase_double_validation_test.yml', + ], 'installable': True, 'active': False, diff --git a/addons/purchase_double_validation/test/purchase_double_validation_test.yml b/addons/purchase_double_validation/test/purchase_double_validation_test.yml new file mode 100644 index 00000000000..ecc8d08a493 --- /dev/null +++ b/addons/purchase_double_validation/test/purchase_double_validation_test.yml @@ -0,0 +1,117 @@ +- + I Create new limit amount from Configure Limit Amount for Purchase wizard. +- + !record {model: purchase.double.validation.installer, id: purchase_double_validation_installer_1}: + limit_amount: 3000 +- + Then I set the Limit Amount +- + !python {model: purchase.double.validation.installer}: | + self.execute(cr, uid, [ref("purchase_double_validation_installer_1")]) +- + In order to test the purchase double validation flow,I start by creating a new product 'MOB1' +- + !record {model: product.product, id: product_product_mob1}: + categ_id: 'product.product_category_3' + cost_method: standard + mes_type: fixed + name: MOB1 + price_margin: 2.0 + procure_method: make_to_stock + property_stock_inventory: stock.location_inventory + property_stock_procurement: stock.location_procurement + property_stock_production: stock.location_production + seller_delay: '1' + standard_price: 2000.0 + supply_method: buy + type: product + uom_id: product.product_uom_unit + uom_po_id: product.product_uom_unit + volume: 0.0 + warranty: 0.0 + weight: 0.0 + weight_net: 0.0 +- + In order to test the purchase double validation flow,I create a new record where "invoice_method" is From Order. +- + Test for purchase double validation in which Total > = Limit Amount. +- + I create purchase order for MOB1 for 3 quantity. +- + !record {model: purchase.order, id: purchase_order_po11}: + company_id: base.main_company + date_order: '2011-01-1' + invoice_method: order + location_id: stock.stock_location_stock + order_line: + - date_planned: '2011-01-11' + name: MOB1 + price_unit: 2000.0 + product_id: 'product_product_mob1' + product_qty: 3.0 + product_uom: product.product_uom_unit + state: draft + partner_address_id: base.res_partner_address_7 + partner_id: base.res_partner_4 + pricelist_id: purchase.list0 +- + Initially purchase order is in the draft state. +- + !assert {model: purchase.order, id: purchase_order_po11}: + - state == 'draft' +- + I confirm the purchase order for MOB1. +- + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_po11} +- + I check that the order which was initially in the draft state has transmit to confirm state for double validation. +- + !assert {model: purchase.order, id: purchase_order_po11}: + - state == 'confirmed' +- + I Approved purchase order by Supplier for MOB1. +- + !workflow {model: purchase.order, action: purchase_approve, ref: purchase_order_po11} +- + I check that the order which was initially in the confirmed state has transmit to approved state. +- + !assert {model: purchase.order, id: purchase_order_po11}: + - state == 'approved' +- + Test for purchase double validation in which Total < Limit Amount. +- + I create purchase order for MOB1 for 1 quantity. +- + !record {model: purchase.order, id: purchase_order_po12}: + company_id: base.main_company + date_order: '2011-01-02' + invoice_method: order + location_id: stock.stock_location_stock + order_line: + - date_planned: '2011-01-12' + name: MOB1 + price_unit: 2000.0 + product_id: 'product_product_mob1' + product_qty: 1.0 + product_uom: product.product_uom_unit + state: draft + partner_address_id: base.res_partner_address_7 + partner_id: base.res_partner_4 + pricelist_id: purchase.list0 +- + Initially purchase order is in the draft state. +- + !assert {model: purchase.order, id: purchase_order_po12}: + - state == 'draft' +- + I confirm the purchase order for MOB1. +- + !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_po12} +- + I check that the order which was initially in the draft state has transmit to approved state. +- + !assert {model: purchase.order, id: purchase_order_po12}: + - state == 'approved' + + + \ No newline at end of file From 43cba5ee71faa5303b76f28059aa89d74a74f885 Mon Sep 17 00:00:00 2001 From: "ksa (Open ERP)" Date: Mon, 10 Jan 2011 14:57:13 +0530 Subject: [PATCH 08/50] [FIX]:Custom Reports do not display any values lp bug: https://launchpad.net/bugs/697554 fixed bzr revid: ksa@tinyerp.co.in-20110110092713-ni6bjztuw45nmmfh --- .../base_report_creator.py | 115 +++++++++--------- 1 file changed, 60 insertions(+), 55 deletions(-) diff --git a/addons/base_report_creator/base_report_creator.py b/addons/base_report_creator/base_report_creator.py index 773458fe3cb..5727646f01c 100644 --- a/addons/base_report_creator/base_report_creator.py +++ b/addons/base_report_creator/base_report_creator.py @@ -23,33 +23,16 @@ from osv import fields, osv from tools.translate import _ from tools import ustr -class report_creator(osv.osv): +class report_result(osv.osv): """ Report Creator """ - _name = "base_report_creator.report" + _name = "base_report_creator_report.result" _description = "Report" model_set_id = False # # Should request only used fields # - def export_data(self, cr, uid, ids, fields_to_export, context=None): - if context is None: - context = {} - data_l = self.read(cr, uid, ids, ['sql_query'], context=context) - final_datas = [] - #start Loop - for record in data_l: - datas = [] - for key in fields_to_export: - value = record.get(key,'') - if isinstance(value, tuple): - datas.append(ustr(value[1])) - else: - datas.append(ustr(value)) - final_datas += [datas] - #End Loop - return {'datas': final_datas} def fields_get(self, cr, user, fields=None, context=None): """ @@ -64,9 +47,9 @@ class report_creator(osv.osv): data = context and context.get('report_id', False) or False if (not context) or 'report_id' not in context: - return super(report_creator, self).fields_get(cr, user, fields, context) + return super(report_result, self).fields_get(cr, user, fields, context) if data: - report = self.browse(cr, user, data) + report = self.pool.get('base_report_creator.report').browse(cr, user, data,context=context) models = {} #Start Loop for model in report.model_ids: @@ -80,12 +63,8 @@ class report_creator(osv.osv): i += 1 else: fields['column_count'] = {'readonly': True, 'type': 'integer', 'string': 'Count', 'size': 64, 'name': 'column_count'} - return fields - # - # Should Call self.fields_get ! - # def fields_view_get(self, cr, user, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): """ Overrides orm field_view_get. @@ -98,8 +77,8 @@ class report_creator(osv.osv): data = context and context.get('report_id', False) or False if (not context) or 'report_id' not in context: - return super(report_creator, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu) - report = self.browse(cr, user, data) + return super(report_result, self).fields_view_get(cr, user, view_id, view_type, context, toolbar=toolbar, submenu=submenu) + report = self.pool.get('base_report_creator.report').browse(cr, user, context.get('report_id'), context=context) models = {} for model in report.model_ids: models[model.model] = self.pool.get(model.model).fields_get(cr, user, context=context) @@ -192,31 +171,24 @@ class report_creator(osv.osv): """ if context is None: context = {} - data = context.get('report_id', False) - res = super(report_creator, self).read(cr, user, ids, fields, context, load) - if (not context) or 'report_id' not in context: - return res - wp = '' - for data in res: - if not data.get('sql_query'): - return res - if self.model_set_id: - wp = [self._id_get(cr, user, data, context) + (' in (%s)' % (','.join(map(lambda x: "'" + str(x) + "'", ids))))] - cr.execute(data['sql_query']) - res = cr.dictfetchall() - fields_get = self.fields_get(cr, user, None, context) - for r in res: - for k in r: - r[k] = r[k] or False - field_dict = fields_get.get(k) - field_type = field_dict and field_dict.get('type', False) or False - if field_type and field_type == 'many2one': - if r[k] == False: - continue - related_name = self.pool.get(field_dict.get('relation')).name_get(cr, user, [r[k]], context)[0] + report_id = context.get('active_id', False) + report = self.pool.get('base_report_creator.report').browse(cr, user, context.get('report_id'), context=context) + sql_qry = report.sql_query + cr.execute(sql_qry) + result = cr.dictfetchall() + fields_get = self.fields_get(cr, user, None, context) + for r in result: + for k in r: + r[k] = r[k] or False + field_dict = fields_get.get(k) + field_type = field_dict and field_dict.get('type', False) or False + if field_type and field_type == 'many2one': + if r[k] == False: + continue + if r[k]: + related_name = self.pool.get(field_dict.get('relation')).name_get(cr, user, [r[k]], context) r[k] = related_name - - return res + return result def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): """ @@ -231,9 +203,9 @@ class report_creator(osv.osv): context_id = context.get('report_id', False) if (not context) or 'report_id' not in context: - return super(report_creator, self).search(cr, user, args, offset, limit, order, context, count) + return {} if context_id: - report = self.browse(cr, user, context_id) + report = self.pool.get('base_report_creator.report').browse(cr, user, context_id, context=context) i = 0 fields = {} for f in report.field_ids: @@ -254,10 +226,42 @@ class report_creator(osv.osv): ctx = context or {} ctx['getid'] = True sql_query = report.sql_query - cr.execute(sql_query, newargs2) + cr.execute(sql_query) # TODO: FILTER result = cr.fetchall() return map(lambda x: x[0], result) +report_result() + + +class report_creator(osv.osv): + """ + Report Creator + """ + _name = "base_report_creator.report" + _description = "Report" + model_set_id = False + # + # Should request only used fields + # + def export_data(self, cr, uid, ids, fields_to_export, context=None): + if context is None: + context = {} + data_l = self.read(cr, uid, ids, ['sql_query'], context=context) + final_datas = [] + #start Loop + for record in data_l: + datas = [] + for key in fields_to_export: + value = record.get(key,'') + if isinstance(value, tuple): + datas.append(ustr(value[1])) + else: + datas.append(ustr(value)) + final_datas += [datas] + #End Loop + return {'datas': final_datas} + + def _path_get(self, cr, uid, models, filter_ids=[]): """ @param cr: the current row, from the database cursor, @@ -465,7 +469,7 @@ class report_creator(osv.osv): 'name': rep.name, 'view_type': 'form', 'view_mode': view_mode, - 'res_model': 'base_report_creator.report', + 'res_model': 'base_report_creator_report.result', 'type': 'ir.actions.act_window', 'context': "{'report_id':%d}" % (rep.id,), 'nodestroy': True, @@ -596,3 +600,4 @@ class report_creator_filter(osv.osv): } report_creator_filter() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + From 8ce6a08a7bf02dda963041cd68660a97baa4766c Mon Sep 17 00:00:00 2001 From: Yogesh Sakhreliya Date: Mon, 10 Jan 2011 15:14:13 +0530 Subject: [PATCH 09/50] [FIX] Fields.function : Correct accessors passed to fields of type integer and integer_big bzr revid: ysa@tinyerp.com-20110110094413-3640rl39pj1r9v1t --- bin/osv/fields.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/osv/fields.py b/bin/osv/fields.py index 6f720ea4669..a6ab3a97072 100644 --- a/bin/osv/fields.py +++ b/bin/osv/fields.py @@ -762,6 +762,11 @@ class function(_column): self._symbol_f = boolean._symbol_f self._symbol_set = boolean._symbol_set + if type in ['integer','integer_big']: + self._symbol_c = integer._symbol_c + self._symbol_f = integer._symbol_f + self._symbol_set = integer._symbol_set + def digits_change(self, cr): if self.digits_compute: t = self.digits_compute(cr) From 0bffe9bf6996c8e363a9db7dd80a560797d26678 Mon Sep 17 00:00:00 2001 From: "ron@tinyerp.com" <> Date: Mon, 10 Jan 2011 17:25:25 +0530 Subject: [PATCH 10/50] [FIX]stock:Backorder Cannot be create on Incoming Shipment lp bug: https://launchpad.net/bugs/697085 fixed bzr revid: ron@tinyerp.com-20110110115525-ctcfqhgisj14abvo --- addons/stock/wizard/stock_partial_picking.py | 172 +++++++++---------- 1 file changed, 77 insertions(+), 95 deletions(-) diff --git a/addons/stock/wizard/stock_partial_picking.py b/addons/stock/wizard/stock_partial_picking.py index ad75c142a96..45a12208101 100644 --- a/addons/stock/wizard/stock_partial_picking.py +++ b/addons/stock/wizard/stock_partial_picking.py @@ -28,54 +28,71 @@ class stock_partial_picking(osv.osv_memory): _name = "stock.partial.picking" _description = "Partial Picking" _columns = { - 'date': fields.datetime('Date', required=True), - 'product_moves_out' : fields.one2many('stock.move.memory.out', 'wizard_id', 'Moves'), - 'product_moves_in' : fields.one2many('stock.move.memory.in', 'wizard_id', 'Moves'), + 'date': fields.datetime('Date', required=True), + 'product_moves_out' : fields.one2many('stock.move.memory.out', 'wizard_id', 'Moves'), + 'product_moves_in' : fields.one2many('stock.move.memory.in', 'wizard_id', 'Moves'), } - def __is_in(self,cr, uid, pick_ids): + def get_picking_type(self, cr, uid, picking, context=None): + picking_type = picking.type + for move in picking.move_lines: + if picking.type == 'in' and move.product_id.cost_method == 'average': + picking_type = 'in' + return picking_type + + def default_get(self, cr, uid, fields, context=None): + """ To get default values for the object. + @param self: The object pointer. + @param cr: A database cursor + @param uid: ID of the user currently logged in + @param fields: List of fields for which we want default values + @param context: A standard dictionary + @return: A dictionary which of fields with values. """ - @return: True if one of the moves has as picking type 'in' - """ - if not pick_ids: - return False + if context is None: + context = {} + + pick_obj = self.pool.get('stock.picking') + res = super(stock_partial_picking, self).default_get(cr, uid, fields, context=context) + picking_ids = context.get('active_ids', []) + if not picking_ids: + return res + + result = [] + for pick in pick_obj.browse(cr, uid, picking_ids, context=context): + pick_type = self.get_picking_type(cr, uid, pick, context=context) + for m in pick.move_lines: + if m.state in ('done', 'cancel'): + continue + result.append(self.__create_partial_picking_memory(m, pick_type)) + + if 'product_moves_in' in fields: + res.update({'product_moves_in': result}) + if 'product_moves_out' in fields: + res.update({'product_moves_out': result}) + if 'date' in fields: + res.update({'date': time.strftime('%Y-%m-%d %H:%M:%S')}) + return res + + def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): + result = super(stock_partial_picking, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu) pick_obj = self.pool.get('stock.picking') - pick_ids = pick_obj.search(cr, uid, [('id','in',pick_ids)]) - - - for pick in pick_obj.browse(cr, uid, pick_ids): - for move in pick.move_lines: - if pick.type == 'in' and move.product_id.cost_method == 'average': - return True - return False - - def __get_picking_type(self, cr, uid, pick_ids): - if self.__is_in(cr, uid, pick_ids): - return "product_moves_in" - else: - return "product_moves_out" - - def view_init(self, cr, uid, fields_list, context=None): - res = super(stock_partial_picking, self).view_init(cr, uid, fields_list, context=context) - return res - - def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False,submenu=False): - result = super(stock_partial_picking, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar,submenu) - - picking_ids = context.get('active_ids', False) - picking_type = self.__get_picking_type(cr, uid, picking_ids) + + for pick in pick_obj.browse(cr, uid, picking_ids, context=context): + picking_type = self.get_picking_type(cr, uid, pick, context=context) _moves_arch_lst = """

- """ % (_('Process Document'), _('Products'), picking_type) + """ % (_('Process Document'), _('Products'), "product_moves_" + picking_type) _moves_fields = result['fields'] + + # add field related to picking type only _moves_fields.update({ - 'product_moves_in' : {'relation': 'stock.move.memory.in', 'type' : 'one2many', 'string' : 'Product Moves'}, - 'product_moves_out' : {'relation': 'stock.move.memory.out', 'type' : 'one2many', 'string' : 'Product Moves'} + 'product_moves_' + picking_type: {'relation': 'stock.move.memory.'+picking_type, 'type' : 'one2many', 'string' : 'Product Moves'}, }) _moves_arch_lst += """ @@ -92,44 +109,22 @@ class stock_partial_picking(osv.osv_memory): result['fields'] = _moves_fields return result - def __create_partial_picking_memory(self, picking, is_in): + def __create_partial_picking_memory(self, picking, pick_type): move_memory = { - 'product_id' : picking.product_id.id, - 'quantity' : picking.product_qty, - 'product_uom' : picking.product_uom.id, - 'prodlot_id' : picking.prodlot_id.id, - 'move_id' : picking.id, + 'product_id' : picking.product_id.id, + 'quantity' : picking.product_qty, + 'product_uom' : picking.product_uom.id, + 'prodlot_id' : picking.prodlot_id.id, + 'move_id' : picking.id, } - if is_in: + if pick_type == 'in': move_memory.update({ - 'cost' : picking.product_id.standard_price, - 'currency' : picking.product_id.company_id.currency_id.id, + 'cost' : picking.product_id.standard_price, + 'currency' : picking.product_id.company_id.currency_id.id, }) return move_memory - def __get_active_stock_pickings(self, cr, uid, context=None): - pick_obj = self.pool.get('stock.picking') - if not context: - context = {} - - res = [] - for pick in pick_obj.browse(cr, uid, context.get('active_ids', []), context): - need_product_cost = (pick.type == 'in') - for m in pick.move_lines: - if m.state in ('done', 'cancel'): - continue - res.append(self.__create_partial_picking_memory(m, need_product_cost)) - - return res - - _defaults = { - 'product_moves_in' : __get_active_stock_pickings, - 'product_moves_out' : __get_active_stock_pickings, - 'date' : lambda *a : time.strftime('%Y-%m-%d %H:%M:%S'), - } - - def do_partial(self, cr, uid, ids, context=None): """ Makes partial moves and pickings done. @param self: The object pointer. @@ -140,6 +135,9 @@ class stock_partial_picking(osv.osv_memory): @return: A dictionary which of fields with values. """ pick_obj = self.pool.get('stock.picking') + move_obj = self.pool.get('stock.move') + location_obj = self.pool.get('stock.location') + picking_ids = context.get('active_ids', False) partial = self.browse(cr, uid, ids[0], context=context) partial_datas = { @@ -147,40 +145,24 @@ class stock_partial_picking(osv.osv_memory): } for pick in pick_obj.browse(cr, uid, picking_ids, context=context): - need_product_cost = (pick.type == 'in') - moves_list = need_product_cost and partial.product_moves_in or partial.product_moves_out - p_moves = {} - for product_move in moves_list: - p_moves[product_move.move_id.id] = product_move - - - for move in pick.move_lines: - if move.state in ('done', 'cancel'): - continue - if not p_moves.get(move.id): - continue - - partial_datas['move%s' % (move.id)] = { - 'product_id' : p_moves[move.id].id, - 'product_qty' : p_moves[move.id].quantity, - 'product_uom' :p_moves[move.id].product_uom.id, - 'prodlot_id' : p_moves[move.id].prodlot_id.id, + picking_type = self.get_picking_type(cr, uid, pick, context=context) + moves_list = picking_type == 'in' and partial.product_moves_in or partial.product_moves_out + + for move in moves_list: + partial_datas['move%s' % (move.move_id.id)] = { + 'product_id': move.id, + 'product_qty': move.quantity, + 'product_uom': move.product_uom.id, + 'prodlot_id': move.prodlot_id.id, } - - - if (move.picking_id.type == 'in') and (move.product_id.cost_method == 'average'): + if (picking_type == 'in') and (move.product_id.cost_method == 'average'): partial_datas['move%s' % (move.id)].update({ - 'product_price' : p_moves[move.id].cost, - 'product_currency': p_moves[move.id].currency.id, + 'product_price' : move.cost, + 'product_currency': move.currency.id, }) pick_obj.do_partial(cr, uid, picking_ids, partial_datas, context=context) return {} - - - stock_partial_picking() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file From cf0da1b7b0988cbd2553fc04ec0aa9c345302506 Mon Sep 17 00:00:00 2001 From: "ksa (Open ERP)" Date: Mon, 10 Jan 2011 17:54:31 +0530 Subject: [PATCH 11/50] [FIX]:add access group on button used to update stock on product lp bug: https://launchpad.net/bugs/699821 fixed bzr revid: ksa@tinyerp.co.in-20110110122431-cf9yex2fw11fy6lp --- addons/stock/product_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index dca180baeec..587c0c84031 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -106,7 +106,7 @@