ibidem 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ibidem/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ #==============================================================================
2
+ # C O P Y R I G H T
3
+ #------------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ #==============================================================================
9
+
10
+ import importlib.metadata
11
+ import os
12
+
13
+ __version__ = importlib.metadata.version('ibidem')
14
+
15
+ ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
16
+ PROCESS = 'footnote'
ibidem/cli.py ADDED
@@ -0,0 +1,78 @@
1
+ #==============================================================================
2
+ # C O P Y R I G H T
3
+ #------------------------------------------------------------------------------
4
+ # Copyright (c) 2019-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ #==============================================================================
9
+
10
+ import utilo
11
+
12
+ import ibidem
13
+
14
+ DESCRIPTION = 'TODO'
15
+
16
+ WORKPLAN = [
17
+ utilo.create_step(
18
+ 'plain',
19
+ inputs=[
20
+ utilo.ResultFile(producer='rawmaker', name='text_text'),
21
+ utilo.ResultFile(producer='rawmaker', name='text_positions'),
22
+ utilo.ResultFile('rawmaker', name='horizontals_horizontals'),
23
+ ],
24
+ output=('plain',),
25
+ ),
26
+ utilo.create_step(
27
+ 'highnote',
28
+ inputs=[
29
+ utilo.ResultFile(producer='rawmaker', name='text_text'),
30
+ utilo.ResultFile(producer='rawmaker', name='text_positions'),
31
+ utilo.ResultFile('rawmaker', name='horizontals_horizontals'),
32
+ ],
33
+ output=('highnote',),
34
+ ),
35
+ utilo.create_step(
36
+ 'result',
37
+ inputs=[
38
+ utilo.ResultFile(producer='footnote', name='highnote_highnote'),
39
+ utilo.ResultFile(producer='footnote', name='plain_plain'),
40
+ ],
41
+ output=('result',),
42
+ ),
43
+ utilo.create_step(
44
+ 'legacy',
45
+ inputs=[
46
+ utilo.ResultFile(producer='footnote', name='result_result'),
47
+ ],
48
+ output=('legacy',),
49
+ ),
50
+ ]
51
+
52
+
53
+ def rename(path):
54
+ if not isinstance(path, str):
55
+ path = [rename(item) for item in path]
56
+ return path
57
+ path = utilo.rreplace(
58
+ path,
59
+ pattern='footnote__legacy_legacy',
60
+ replace='groupme__footer_footerheader',
61
+ )
62
+ return path
63
+
64
+
65
+ def main():
66
+ utilo.featurepack(
67
+ workplan=WORKPLAN,
68
+ root=ibidem.ROOT,
69
+ featurepackage='ibidem.feature',
70
+ config=utilo.FeaturePackConfig(
71
+ description=DESCRIPTION,
72
+ multiprocessed=True,
73
+ name=ibidem.PROCESS,
74
+ pages=True,
75
+ rename=rename,
76
+ version=ibidem.__version__,
77
+ ),
78
+ )
ibidem/config.py ADDED
@@ -0,0 +1,15 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import configos
11
+ import texmex
12
+
13
+ FOOTER_SEPARATOR_WIDTH_MIN = configos.HV_INT_PLUS(default=70)
14
+
15
+ VISIBLE = texmex.TextState.VISIBLE | texmex.TextState.FOOTNOTE
@@ -0,0 +1,8 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
@@ -0,0 +1,42 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import serializeraw
11
+
12
+ import ibidem.config
13
+ import ibidem.strategy.highnote
14
+ import ibidem.utils
15
+
16
+
17
+ def work(
18
+ text: str,
19
+ textpositions: str,
20
+ horizontals: str,
21
+ pages=None,
22
+ ) -> str:
23
+ # load
24
+ horizontals = serializeraw.load_horizontals(
25
+ horizontals,
26
+ pages=pages,
27
+ width_min=ibidem.config.FOOTER_SEPARATOR_WIDTH_MIN,
28
+ )
29
+ ptns = serializeraw.ptn_fromfile(
30
+ text,
31
+ textpositions,
32
+ pages=pages,
33
+ state=ibidem.config.VISIBLE,
34
+ )
35
+ ptns = ibidem.utils.rotate_ifrequired(ptns)
36
+ strategy = ibidem.strategy.highnote.HighnoteStrategy(
37
+ horizontals=horizontals,
38
+ ptns=ptns,
39
+ )
40
+ result = strategy.result()
41
+ dumped = serializeraw.dump_headerfooter(result)
42
+ return dumped
@@ -0,0 +1,14 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import utilo
11
+
12
+
13
+ def work(xresult) -> str:
14
+ return utilo.file_read(xresult)
@@ -0,0 +1,41 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import serializeraw
11
+
12
+ import ibidem.config
13
+ import ibidem.strategy.plainmoving
14
+
15
+
16
+ def work(
17
+ text: str,
18
+ textpositions: str,
19
+ horizontals: str,
20
+ pages=None,
21
+ ) -> str:
22
+ # load
23
+ horizontals = serializeraw.load_horizontals(
24
+ horizontals,
25
+ pages=pages,
26
+ width_min=ibidem.config.FOOTER_SEPARATOR_WIDTH_MIN,
27
+ )
28
+ ptns = serializeraw.ptn_fromfile(
29
+ text,
30
+ textpositions,
31
+ pages=pages,
32
+ state=ibidem.config.VISIBLE,
33
+ )
34
+ ptns = ibidem.utils.rotate_ifrequired(ptns)
35
+ strategy = ibidem.strategy.plainmoving.PlainMovingStrategy(
36
+ horizontals=horizontals,
37
+ ptns=ptns,
38
+ )
39
+ result = strategy.result()
40
+ dumped = serializeraw.dump_headerfooter(result)
41
+ return dumped
@@ -0,0 +1,142 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2019-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+ """Footer Extraction Step
10
+ =============================
11
+
12
+ TODO:
13
+ what should we do with empty header/footer
14
+ """
15
+
16
+ import collections
17
+
18
+ import iamraw
19
+ import serializeraw
20
+ import utilo
21
+
22
+
23
+ def work(
24
+ xhighnote: str,
25
+ xplain: str,
26
+ pages=None,
27
+ ) -> str:
28
+ """Extract footer and header area out of horizontal lines.
29
+
30
+ Returns:
31
+ Dumped list with top and bottom border, which separates the
32
+ content from the footer and or header, for every page
33
+ """
34
+ highnote = serializeraw.load_headerfooter(
35
+ xhighnote,
36
+ pages=pages,
37
+ )
38
+ plain = serializeraw.load_headerfooter(
39
+ xplain,
40
+ pages=pages,
41
+ )
42
+ # select the best one
43
+ result = judge_strategy((
44
+ highnote,
45
+ plain,
46
+ ))
47
+ validate(result)
48
+ # dump
49
+ dumped = serializeraw.dump_headerfooter(result)
50
+ return dumped
51
+
52
+
53
+ def judge_strategy(
54
+ results: list[iamraw.PageContentFooterHeaders],
55
+ ) -> iamraw.PageContentFooterHeaders:
56
+ """Decide which results fits best.
57
+
58
+ Zip result of different strategies. Sometimes there are multiple
59
+ options, therefore we have to use the priorities below.
60
+
61
+ Sources/Concept:
62
+
63
+ - MovingFooter: footer (first prio)
64
+ - Pages: footer (second prio)
65
+ - FixedFooter: header and footer (third prio)
66
+ - Common: header (last prio)
67
+ - PlainMoving:
68
+
69
+ Args:
70
+ results: lists of `ibidem.FootnoteDetectionStrategy`.result
71
+ Returns:
72
+ list of zipped result
73
+ """
74
+ assert results is not None, 'require list of strategy results'
75
+ result = []
76
+ for pdfpage, (
77
+ moving,
78
+ plainmoving,
79
+ ) in utilo.sync_pages(results):
80
+ footer = moving.footer if moving else None
81
+ footer_best = 'moving' if moving else None
82
+ # strategy: moving
83
+ if moving and moving.footer and moving.footer.notes:
84
+ footer = moving.footer
85
+ footer_best = 'moving'
86
+ # strategy: plain
87
+ if not (moving and moving.footer) and plainmoving and plainmoving.footer: # yapf:disable
88
+ # use plain moving only if no other strategy works
89
+ footer = plainmoving.footer
90
+ footer_best = 'plain'
91
+ # log footer best
92
+ if footer_best:
93
+ utilo.verbose(f'footer: {pdfpage} {footer_best}')
94
+ current = iamraw.PageContentFooterHeader(
95
+ footer=footer,
96
+ page=pdfpage,
97
+ )
98
+ result.append(current)
99
+
100
+ page_order = [item.page for item in result]
101
+ assert utilo.isascending(page_order), page_order
102
+ return result
103
+
104
+
105
+ def quality(results: list) -> tuple:
106
+ """Determine quality[0.0, 1.0] of every extraction strategy."""
107
+ # count number of page
108
+ pages = set()
109
+ # count result for every strategy
110
+ counter = collections.defaultdict(int)
111
+ for pdfpage, data in utilo.sync_pages(results):
112
+ pages.add(pdfpage)
113
+ for index, item in enumerate(data):
114
+ if not item:
115
+ continue
116
+ counter[index] += 1
117
+ result = tuple(counter[index] / len(pages) if pages else 0
118
+ for index, _ in enumerate(results))
119
+ return result
120
+
121
+
122
+ def validate(items: list):
123
+ """Validate list of pageable items.
124
+
125
+ If some `page` attribute is duplicated, raise ValueError.
126
+
127
+ Args:
128
+ items(list): list of objects with <page,content>
129
+ Raises:
130
+ ValueError: if some page attribute is duplicated.
131
+ """
132
+ # TODO: REMOVE AFTER UPGRADING IAMRAW
133
+ counter = collections.Counter()
134
+ for item in items:
135
+ counter[item.page] += 1
136
+ msg = []
137
+ for page, value in counter.most_common():
138
+ if value <= 1:
139
+ continue
140
+ msg.append(f'duplicated page: {page} ({value})')
141
+ if msg:
142
+ raise ValueError(utilo.NEWLINE.join(msg))
ibidem/layout.py ADDED
@@ -0,0 +1,307 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2021-2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================
9
+
10
+ import dataclasses
11
+ import math
12
+
13
+ import configos
14
+ import iamraw
15
+ import texmex
16
+ import utilo
17
+
18
+ import ibidem.utils
19
+
20
+ HIGHNOTE_VERTICAL_LINE_DIFF_MAX = configos.HV_FLOAT_PLUS(default=15.0)
21
+
22
+ FOOTNOTE_X0_MAX = configos.HolyTable(items=(
23
+ (440, 100), # TODO: US Letter?
24
+ (550, 150), # DINA4
25
+ ))
26
+ FOOTNOTE_X1_MAX = configos.HolyTable(items=(
27
+ (440, 100 + 200), # TODO: US Letter?
28
+ (550, 150 + 200), # DINA4
29
+ ))
30
+
31
+ FOOTNOTE_RATE_MIN = configos.HV_PERCENT_PLUS(default=50)
32
+
33
+
34
+ def group_footnote_area(content) -> list:
35
+ connected_neighbors = connect_neighbors(content)
36
+ if not connected_neighbors:
37
+ return []
38
+ result = []
39
+ has_highnote = 0
40
+ for group in connected_neighbors:
41
+ splitted = split_textinfo(group)
42
+ if any(high for high, _ in splitted):
43
+ has_highnote += 1
44
+ merged = merge_online(splitted)
45
+ result.extend(merged)
46
+ result = merge_after(result)
47
+ rate = utilo.rate_rel(has_highnote, len(connected_neighbors))
48
+ if rate < FOOTNOTE_RATE_MIN:
49
+ if len(result) == 1:
50
+ return result
51
+ utilo.debug(f'no highnotes: {rate} detected, skip footnote result')
52
+ utilo.verbose(result)
53
+ return []
54
+ return result
55
+
56
+
57
+ def merge_after(items: list) -> list:
58
+ """Make merger more robust against false formatted footnotes.
59
+
60
+ Merge footnote text which is under highnote on the left side.
61
+
62
+ See:
63
+ - bachelor041a p14
64
+ - master091b p54
65
+ """
66
+ if not items:
67
+ return []
68
+ result = [items[0]]
69
+ for highnote, content in items[1:]:
70
+ if highnote is not None:
71
+ # normal footnote
72
+ result.append((highnote, content))
73
+ continue
74
+ highnote_before = result[-1][0] is not None
75
+ if highnote_before:
76
+ # merge them
77
+ for item in content.style.content:
78
+ item.start += len(result[-1][1].text)
79
+ item.end += len(result[-1][1].text)
80
+ result[-1][1].text += content.text
81
+ result[-1][1].style.content.extend(content.style.content)
82
+ continue
83
+ # footnote without highnote
84
+ result.append((None, content))
85
+ return result
86
+
87
+
88
+ def connect_neighbors(items) -> list:
89
+ if not items:
90
+ return []
91
+ result = [[items[0]]]
92
+ for item in items[1:]:
93
+ before = result[-1][-1]
94
+ if connected(before.bounding, item.bounding):
95
+ result[-1].append(item)
96
+ else:
97
+ result.append([item])
98
+ return result
99
+
100
+
101
+ def split_textinfo(content) -> list:
102
+ """Split text by `hightnote` and preserve TextInfo.
103
+
104
+ Go line by line from top to bottom. Collect lines till highnote
105
+ occurs. If highnote occurs merge content and add to result.
106
+
107
+ Returns:
108
+ list of a tuple of highnote and content
109
+ """
110
+ assert isinstance(content, list), type(content)
111
+ result = []
112
+ highnote = None
113
+ collected = []
114
+ for item in content:
115
+ for style in item.style.content:
116
+ if ishighnote(style, item.text):
117
+ if collected:
118
+ result.append((highnote, union(collected)))
119
+ collected = []
120
+ style = style.copy()
121
+ highnote = texmex.TextInfo(
122
+ text=item.text[style.start:style.end],
123
+ style=style,
124
+ bounding=char_bounding(item.bounding, item.text, style),
125
+ )
126
+ style.start = 0
127
+ style.end = len(highnote.text)
128
+ else:
129
+ bounding = iamraw.split_x(
130
+ bounding=item.bounding,
131
+ part=style.start,
132
+ parts=style.end,
133
+ width=len(item.text),
134
+ )
135
+ collected.append(TextChunk(
136
+ item.text,
137
+ style,
138
+ bounding=bounding,
139
+ ))
140
+ if highnote or collected:
141
+ # there is always content left at the end
142
+ result.append((highnote, union(collected)))
143
+ return result
144
+
145
+
146
+ def merge_online(items) -> list:
147
+ """Ensure that high notes are located on a vertical line.
148
+
149
+ Therefore we have to ignore highnotes which are located inside the
150
+ text and not part of the text flow.
151
+
152
+ Steps:
153
+ 1. Determine the most left highnotes
154
+ 2. Adjust highnotes on most left one
155
+ 3. Merge other highnotes into text
156
+ """
157
+ if not items:
158
+ return []
159
+ result = []
160
+ # skip None-Highnotes => if item
161
+ with_highnote = [high.bounding.x0 for high, _ in items if high]
162
+ mostleft = min(with_highnote) if with_highnote else None
163
+ high, collected = None, []
164
+ for highnote, content in items:
165
+ if not highnote:
166
+ result.append((None, content))
167
+ continue
168
+ diff = math.fabs(highnote.bounding.x0 - mostleft)
169
+ if diff > HIGHNOTE_VERTICAL_LINE_DIFF_MAX:
170
+ # highnote in content
171
+ collected.append(
172
+ shrink_tostyle(
173
+ highnote.text,
174
+ highnote.style,
175
+ bounding=highnote.bounding,
176
+ ))
177
+ collected.extend([
178
+ shrink_tostyle(content.text, style, bounding=content.bounding)
179
+ for style in content.style
180
+ ])
181
+ else:
182
+ # new highnotes
183
+ if high:
184
+ result.append((high, union(collected)))
185
+ high = highnote
186
+ collected = [
187
+ shrink_tostyle(content.text, style, bounding=content.bounding)
188
+ for style in content.style
189
+ ]
190
+ notempty = collected or high
191
+ if notempty:
192
+ # do not add empty items
193
+ result.append((high, union(collected)))
194
+ return result
195
+
196
+
197
+ # increase word diff at the start of the line to merge huge gap between
198
+ # footnote number and footnote text.
199
+ LEFTRIGHT_DIFF_MAX = configos.HolyTable(items=(
200
+ (0, 40),
201
+ (100, 40),
202
+ (120, 40),
203
+ (150, 30),
204
+ (200, 20),
205
+ (300, 20),
206
+ (400, 20),
207
+ (500, 20),
208
+ ))
209
+
210
+ SAMEORIGIN_DIFF_MAX = configos.HV_FLOAT_PLUS(default=35.0)
211
+
212
+ SAMELINE_DIFF_MAX = configos.HV_FLOAT_PLUS(default=5.0)
213
+
214
+ UNDERFIRST_DIFF_MAX = configos.HV_FLOAT_PLUS(default=10.0)
215
+
216
+
217
+ def connected(first: tuple, second: tuple) -> bool:
218
+ leftright_diff_max = LEFTRIGHT_DIFF_MAX(first.x0)
219
+ # words are neighbors
220
+ leftright = utilo.near(
221
+ first.x1,
222
+ second.x0,
223
+ diff=leftright_diff_max,
224
+ )
225
+ # plus indention
226
+ sameorigin = utilo.near(
227
+ first.x0,
228
+ second.x0,
229
+ diff=SAMEORIGIN_DIFF_MAX,
230
+ )
231
+ sameline = utilo.near(
232
+ first.y0,
233
+ second.y0,
234
+ diff=SAMELINE_DIFF_MAX,
235
+ )
236
+ underfirst = utilo.near(
237
+ first.y1,
238
+ second.y0,
239
+ diff=UNDERFIRST_DIFF_MAX,
240
+ )
241
+ result = (leftright or sameorigin) and (sameline or underfirst)
242
+ return result
243
+
244
+
245
+ @dataclasses.dataclass
246
+ class TextChunk:
247
+ text: str = None
248
+ style: texmex.TextStyle = None
249
+ bounding: iamraw.BoundingBox = None
250
+
251
+
252
+ TextChunks = list[TextChunk]
253
+
254
+
255
+ def shrink_tostyle(text: str, style, bounding=None) -> TextChunk:
256
+ text = text[style.start:style.end]
257
+ style = style.copy()
258
+ style.start, style.end = 0, len(text)
259
+ # TODO: ADD BOUNDING ADJUSTMENT, IN THE CURRENT CASE, WE DO NOT SHRINK
260
+ # THE BOUNDING
261
+ return TextChunk(text, style, bounding)
262
+
263
+
264
+ def union(chunks: TextChunks) -> texmex.TextInfo:
265
+ raw = ''
266
+ content = []
267
+ for chunk in chunks: # pylint:disable=W0612
268
+ start = len(raw)
269
+ raw += chunk.text[chunk.style.start:chunk.style.end]
270
+ end = len(raw)
271
+ section_style = chunk.style.copy()
272
+ section_style.start, section_style.end = start, end
273
+ content.append(section_style)
274
+ bounding = [item.bounding for item in chunks if item.bounding]
275
+ bounding = utilo.rect_max(bounding) if bounding else None
276
+ result = texmex.TextInfo(
277
+ text=raw,
278
+ style=texmex.TextStyle(content=content),
279
+ bounding=bounding,
280
+ )
281
+ return result
282
+
283
+
284
+ def char_bounding(
285
+ bounding: iamraw.BoundingBox,
286
+ text: str,
287
+ style: texmex.TextStyle,
288
+ ) -> iamraw.BoundingBox:
289
+ width = bounding.x1 - bounding.x0
290
+ char_width = width / len(text)
291
+ x0 = bounding.x0 + char_width * style.start
292
+ x1 = bounding.x0 + char_width * style.end
293
+ result = iamraw.BoundingBox(x0, bounding.y0, x1, bounding.y1)
294
+ return result
295
+
296
+
297
+ HIGHNOTE_RISE_MIN = configos.HV_FLOAT_PLUS(default=3.0)
298
+
299
+
300
+ def ishighnote(style, text: str) -> bool:
301
+ highnote_occurs = style.rise >= HIGHNOTE_RISE_MIN
302
+ if not highnote_occurs:
303
+ return False
304
+ text = text[style.start:style.end].strip()
305
+ if ibidem.utils.NUMBER.match(text):
306
+ return True
307
+ return False
@@ -0,0 +1,8 @@
1
+ # =============================================================================
2
+ # C O P Y R I G H T
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (c) 2022 by Helmut Konrad Fahrendholz. All rights reserved.
5
+ # This file is property of Helmut Konrad Fahrendholz. Any unauthorized copy,
6
+ # use or distribution is an offensive act against international law and may
7
+ # be prosecuted under federal law. Its content is company confidential.
8
+ # =============================================================================