├── data
├── documents
│ └── .gitignore
├── models
│ └── .gitignore
├── results
│ └── .gitignore
└── stop_words
│ └── .gitignore
├── .gitignore
├── README.md
├── keywords_tfidf.py
├── corpus.py
├── keywords_lda.py
└── LICENSE
/data/documents/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/data/models/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/data/results/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/data/stop_words/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 |
55 | # Sphinx documentation
56 | docs/_build/
57 |
58 | # PyBuilder
59 | target/
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Keyword Generator
2 | =================
3 |
4 | The Keyword Generator, created in collaboration with KB Researcher-in-residence Pim Huijnen, is a command-line tool that offers two methods to extract relevant keywords from a collection of sample texts provided by the user:
5 |
6 | 1) `keywords_tfidf.py`, extracting keywords based on tf-idf scores. Options:
7 |
8 | - -k : number of keywords to be generated (default 10)
9 | - -d : document length (the documents provided by the user will be split into parts containing the specified number of words; by default the documents will not be split.)
10 |
11 | 2) `keywords_lda.py`, extracting keywords based on either [Gensim](https://radimrehurek.com/gensim/)'s or [Mallet](http://mallet.cs.umass.edu)'s implementation of LDA topic modeling. Options:
12 |
13 | - -t : number of topics (default 10)
14 | - -w : number of words per topic (default 10)
15 | - -k : number of keywords (default 10)
16 | - -d : document length (the documents provided by the user will be split into parts containing the specified number of words; by default the documents will not be split.)
17 | - m : mallet path (full path to the [Mallet](http://mallet.cs.umass.edu) executable; if not provided, [Gensim](https://radimrehurek.com/gensim/)'s LDA implementation will be used to generate topics.)
18 |
19 | Documents are to be placed in the `data/documents` folder, stop word lists in the `data/stop_words` folder. The keyword lists and any topics and topic distributions generated will be saved in the `data/results` folder.
20 |
21 | The Keyword Generator currently uses Python 2.7, and [Gensim](https://radimrehurek.com/gensim/) and [Mallet](http://mallet.cs.umass.edu) need to be installed locally.
22 |
23 | Some examples of commands:
24 | ```
25 | $ ./keywords_tfidf.py
26 | $ ./keywords_tfidf.py -k 20 -d 100
27 | $ ./keywords_lda.py -k 10 -d 100 -t 5 -w 20
28 | $ ./keywords_lda.py -k 10 -d 100 -t 5 -w 20 -m /opt/mallet-2.0.7/bin/mallet
29 | ```
30 |
--------------------------------------------------------------------------------
/keywords_tfidf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | #
5 | # Keyword Generator
6 | #
7 | # Copyright (C) 2015 Juliette Lonij, Koninklijke Bibliotheek -
8 | # National Library of the Netherlands
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU Lesser General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU Lesser General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU Lesser General Public License
21 | # along with this program. If not, see .
22 |
23 |
24 | import argparse
25 | import codecs
26 | import corpus as cp
27 | import csv
28 | import gensim
29 | import math
30 | import operator
31 | import os
32 | import sys
33 | import time
34 |
35 |
36 | # Generate keywords
37 | def generate_keywords(tfidf_scores, num_keywords):
38 | print('Generating keywords...')
39 | keywords = {}
40 |
41 | # Sum of scores for token in all documents
42 | for doc in tfidf_scores:
43 | for t in doc:
44 | key = t[0]
45 | score = t[1]
46 | if key in keywords:
47 | keywords[key] += score
48 | else:
49 | keywords[key] = score
50 |
51 | # Sort keywords by highest score
52 | sorted_keywords = sorted(keywords.items(), key=operator.itemgetter(1),
53 | reverse=True)
54 |
55 | return sorted_keywords[:num_keywords]
56 |
57 |
58 | def print_keywords(keywords, dictionary):
59 | print('Keywords generated:')
60 | for i, k in enumerate(keywords):
61 | print('(%i) %s [%s]' % (i + 1, dictionary.get(k[0]), k[1]))
62 |
63 |
64 | def save_keywords(keywords, dictionary):
65 | timestamp = int(time.time())
66 | with open('data' + os.sep + 'results' + os.sep + str(timestamp) +
67 | '_keywords' + '.csv', 'wb') as f:
68 | csv_writer = csv.writer(f, delimiter='\t')
69 | for k in keywords:
70 | csv_writer.writerow([dictionary.get(k[0]).encode('utf-8'),
71 | str(k[1])])
72 |
73 |
74 | if __name__ == '__main__':
75 | if sys.stdout.encoding != 'UTF-8':
76 | sys.stdout = codecs.getwriter('utf-8')(sys.stdout, 'strict')
77 |
78 | parser = argparse.ArgumentParser()
79 | parser.add_argument('-k', required=False, type=int, default=10,
80 | help='number of keywords')
81 | parser.add_argument('-d', required=False, type=int, default=0,
82 | help='document length')
83 | args = parser.parse_args()
84 |
85 | num_keywords, doc_length = vars(args)['k'], vars(args)['d']
86 |
87 | doc_folder = 'data' + os.sep + 'documents'
88 | stop_folder = 'data' + os.sep + 'stop_words'
89 |
90 | corpus, dictionary = cp.MyCorpus(doc_folder, stop_folder, doc_length).load()
91 | tfidf = gensim.models.TfidfModel(corpus)
92 | tfidf_scores = tfidf[corpus]
93 | keywords = generate_keywords(tfidf_scores, num_keywords)
94 |
95 | print_keywords(keywords, dictionary)
96 | save_keywords(keywords, dictionary)
97 |
--------------------------------------------------------------------------------
/corpus.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | #
5 | # Keyword Generator
6 | #
7 | # Copyright (C) 2015 Juliette Lonij, Koninklijke Bibliotheek -
8 | # National Library of the Netherlands
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU Lesser General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU Lesser General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU Lesser General Public License
21 | # along with this program. If not, see .
22 |
23 |
24 | import gensim
25 | import os
26 | import warnings
27 |
28 |
29 | # Pre-process input documents
30 | def get_documents(path, doc_length):
31 | print('Processing documents ...')
32 | docs = []
33 | for filename in os.listdir(path):
34 | if filename.endswith('.txt'):
35 | with open(path + '/' + filename) as f:
36 | s = decode(f.read())
37 | if not s:
38 | print('Could not decode file contents, skipping.')
39 | continue
40 | if doc_length > 0:
41 | for piece in splitter(doc_length, s):
42 | docs.append(piece)
43 | else:
44 | docs.append(s)
45 | num_docs = len(docs)
46 | print('Number of documents: %i' % num_docs)
47 | return docs
48 |
49 |
50 | # Split large documents into smaller parts
51 | def splitter(n, s):
52 | pieces = s.split()
53 | return (' '.join(pieces[i:i+n]) for i in xrange(0, len(pieces), n))
54 |
55 |
56 | # Set stop word list
57 | def get_stop_words(path):
58 | print('Getting stop words ...')
59 | stop_words = []
60 | for filename in os.listdir(path):
61 | if filename.endswith('.txt'):
62 | with open(path + os.sep + filename) as f:
63 | s = decode(f.read())
64 | if not s:
65 | print('Could not decode file contents, skipping.')
66 | continue
67 | stop_words += [sw.lower() for sw in s.split() if sw]
68 | num_words = len(stop_words)
69 | print('Number of stop words: %i' % num_words)
70 | return stop_words
71 |
72 |
73 | # Try to decode various possible encodings
74 | def decode(s):
75 | encodings = ['utf-8', 'iso-8859-1']
76 | decoded = None
77 | for e in encodings:
78 | try:
79 | decoded = s.decode(e)
80 | break
81 | except UnicodeDecodeError:
82 | continue
83 | return decoded
84 |
85 |
86 | # Read documents, tokenize
87 | def iter_docs(doc_list, stop_list):
88 | for doc in doc_list:
89 | yield (x for x in gensim.utils.tokenize(doc, lowercase=True, deacc=True,
90 | errors='replace') if x not in stop_list)
91 |
92 |
93 | # Corpus class
94 | class MyCorpus(object):
95 | warnings.filterwarnings('ignore')
96 | model_folder = 'data/models'
97 |
98 | def __init__(self, doc_dir, stop_dir, doc_length):
99 | self.doc_list = get_documents(doc_dir, doc_length)
100 | self.stop_list = get_stop_words(stop_dir)
101 |
102 | print('Generating dictionary ...')
103 | self.dictionary = gensim.corpora.Dictionary(iter_docs(self.doc_list,
104 | self.stop_list))
105 | no_below = 1 if len(self.doc_list) <= 10 else 2
106 | no_above = 1 if len(self.doc_list) <= 10 else 0.95
107 | self.dictionary.filter_extremes(no_below=no_below, no_above=no_above,
108 | keep_n=100000)
109 | num_tokens = len(self.dictionary.items())
110 | print('Number of unique tokens in dictionary: %s' % num_tokens)
111 | self.dictionary.save(os.path.join(self.model_folder, 'kwg.dict'))
112 |
113 | print('Generating corpus ...')
114 | gensim.corpora.MmCorpus.serialize(os.path.join(self.model_folder,
115 | 'kwg.mm'), self)
116 |
117 | def __iter__(self):
118 | for tokens in iter_docs(self.doc_list, self.stop_list):
119 | yield self.dictionary.doc2bow(tokens)
120 |
121 | def load(self):
122 | dictionary = gensim.corpora.Dictionary.load(os.path.join(self.model_folder,
123 | 'kwg.dict'))
124 | corpus = gensim.corpora.MmCorpus(os.path.join(self.model_folder,
125 | 'kwg.mm'))
126 | return corpus, dictionary
127 |
--------------------------------------------------------------------------------
/keywords_lda.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | #
5 | # Keyword Generator
6 | #
7 | # Copyright (C) 2015 Juliette Lonij, Koninklijke Bibliotheek -
8 | # National Library of the Netherlands
9 | #
10 | # This program is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU Lesser General Public License as published by
12 | # the Free Software Foundation, either version 3 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # This program is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU Lesser General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU Lesser General Public License
21 | # along with this program. If not, see .
22 |
23 |
24 | import argparse
25 | import codecs
26 | import corpus as cp
27 | import csv
28 | import gensim
29 | import math
30 | import operator
31 | import os
32 | import sys
33 | import time
34 |
35 | from builtins import input
36 |
37 |
38 | # Exclude topics
39 | def exclude_topics(topics):
40 | print_topics(topics)
41 | inp = input('Enter topics to exclude, separated by commas: ')
42 | if inp == '':
43 | return topics
44 | excl_topics = [int(i.strip()) for i in inp.split(',')]
45 | excl_topics.sort(reverse=True)
46 | for i in excl_topics:
47 | del topics[i-1]
48 | return topics
49 |
50 |
51 | # Print topics
52 | def print_topics(topics):
53 | print('Topics generated:')
54 | for i, topic in enumerate(topics):
55 | print('(' + str(i + 1) + ') ' + ', '.join([t[1] for t in topic]))
56 |
57 |
58 | # Generate keywords
59 | def generate_keywords(corpus, dictionary, topics, num_keywords):
60 | print('Generating keywords...')
61 | keywords = {}
62 |
63 | # Sum of probabilities for token in all topics
64 | for topic in topics:
65 | for t in topic:
66 | token = t[1]
67 | pr = t[0]
68 | if token in keywords:
69 | keywords[token] += pr
70 | else:
71 | keywords[token] = pr
72 |
73 | # Probability for each token multiplied by token frequency
74 | matrix = gensim.matutils.corpus2csc(corpus)
75 | for token, pr in keywords.items():
76 | for d in dictionary.items():
77 | if d[1] == token:
78 | token_index = d[0]
79 | break
80 | token_row = matrix.getrow(token_index)
81 | token_freq = token_row.sum(1).item()
82 | keywords[token] = pr * math.log(token_freq)
83 |
84 | # Sort keywords by highest score
85 | sorted_keywords = sorted(keywords.items(), key=operator.itemgetter(1),
86 | reverse=True)
87 |
88 | return sorted_keywords[:num_keywords]
89 |
90 |
91 | def print_keywords(keywords):
92 | print('Keywords generated:')
93 | for i, k in enumerate(keywords):
94 | print('(%i) %s [%s]' % (i + 1, k[0], k[1]))
95 |
96 |
97 | def save_keywords(keywords):
98 | timestamp = int(time.time())
99 | with open('data' + os.sep + 'results' + os.sep + str(timestamp) +
100 | '_keywords' + '.csv', 'wb') as f:
101 | csv_writer = csv.writer(f, delimiter='\t')
102 | for k in keywords:
103 | csv_writer.writerow([k[0].encode('utf-8'), str(k[1])])
104 |
105 |
106 | def save_topics(topics):
107 | timestamp = int(time.time())
108 | with open('data' + os.sep + 'results' + os.sep + str(timestamp) +
109 | '_topics' + '.csv', 'wb') as f:
110 | csv_writer = csv.writer(f, delimiter='\t')
111 | for topic in topics:
112 | csv_writer.writerow([t[1].encode('utf-8') for t in topic])
113 | csv_writer.writerow([str(t[0]) for t in topic])
114 |
115 |
116 | def save_distributions(distributions):
117 | timestamp = int(time.time())
118 | with open('data' + os.sep + 'results' + os.sep + str(timestamp) +
119 | '_distributions' + '.csv', 'wb') as f:
120 | csv_writer = csv.writer(f, delimiter='\t')
121 | csv_writer.writerow(['Document'] + ['Topic ' + str(i + 1) for i in
122 | range(len(distributions[0]))])
123 | for i, dist in enumerate(distributions):
124 | csv_writer.writerow([str(i)] + ['{0:.5f}'.format(t[1]) for t in
125 | dist])
126 |
127 |
128 | if __name__ == '__main__':
129 | if sys.stdout.encoding != 'UTF-8':
130 | sys.stdout = codecs.getwriter('utf-8')(sys.stdout, 'strict')
131 |
132 | parser = argparse.ArgumentParser()
133 | parser.add_argument('-t', required=False, type=int, default=10,
134 | help='number of topics')
135 | parser.add_argument('-w', required=False, type=int, default=10,
136 | help='number of words per topic')
137 | parser.add_argument('-k', required=False, type=int, default=10,
138 | help='number of keywords')
139 | parser.add_argument('-d', required=False, type=int, default=10,
140 | help='document length')
141 | parser.add_argument('-m', required=False, type=str, help='Mallet path')
142 | args = parser.parse_args()
143 |
144 | num_keywords, doc_length = vars(args)['k'], vars(args)['d']
145 | num_topics, num_words = vars(args)['t'], vars(args)['w']
146 | mallet_path = vars(args)['m']
147 |
148 | doc_folder = 'data' + os.sep + 'documents'
149 | stop_folder = 'data' + os.sep + 'stop_words'
150 |
151 | corpus, dictionary = cp.MyCorpus(doc_folder, stop_folder, doc_length).load()
152 |
153 | if mallet_path:
154 | print('Generating model with Mallet LDA ...')
155 | lda = gensim.models.wrappers.LdaMallet(mallet_path, corpus=corpus,
156 | id2word=dictionary, num_topics=num_topics)
157 | topics = lda.show_topics(num_topics=num_topics, num_words=num_words,
158 | formatted=False)
159 | distributions = [dist for dist in lda.load_document_topics()]
160 | else:
161 | print('Generating model with Gensim LDA ...')
162 | lda = gensim.models.LdaModel(corpus, id2word=dictionary,
163 | num_topics=num_topics, alpha='auto', chunksize=1, eval_every=1)
164 | gensim_topics = [t[1] for t in lda.show_topics(num_topics=num_topics,
165 | num_words=num_words, formatted=False)]
166 | topics = [[(i[1], i[0]) for i in t] for t in gensim_topics]
167 | distributions = []
168 | matrix = gensim.matutils.corpus2csc(corpus)
169 | for i in range(matrix.get_shape()[1]):
170 | bow = gensim.matutils.scipy2sparse(matrix.getcol(i).transpose())
171 | distributions.append(lda.get_document_topics(bow, 0))
172 |
173 | topics = exclude_topics(topics)
174 | keywords = generate_keywords(corpus, dictionary, topics, num_keywords)
175 |
176 | print_keywords(keywords)
177 | save_keywords(keywords)
178 | save_topics(topics)
179 | save_distributions(distributions)
180 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------