BibexPy 1.0.0__tar.gz

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.

Potentially problematic release.


This version of BibexPy might be problematic. Click here for more details.

@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.1
2
+ Name: BibexPy
3
+ Version: 1.0.0
4
+ Summary: A Python library for advanced bibliography management and export.
5
+ Home-page: https://github.com/bcankara/BibexPy
6
+ Author: bcankara
7
+ Author-email: info@bibexpy.com
8
+ License: MIT
9
+ Project-URL: Bug Tracker, https://github.com/bcankara/BibexPy/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: python-dotenv==1.0.0
17
+ Requires-Dist: pandas>=2.0.0
18
+ Requires-Dist: openpyxl>=3.1.2
19
+ Requires-Dist: numpy>=1.24.0
20
+ Requires-Dist: requests>=2.31.0
21
+ Requires-Dist: scikit-learn>=1.3.0
22
+ Requires-Dist: scipy>=1.11.0
23
+ Requires-Dist: tqdm>=4.65.0
24
+ Requires-Dist: colorama>=0.4.6
25
+ Requires-Dist: xlrd>=2.0.1
26
+ Requires-Dist: xlsxwriter>=3.1.0
27
+ Requires-Dist: typing-extensions>=4.7.0
28
+
29
+ # BibexPy
30
+
31
+ **Harmonizing the Bibliometric Symphony of Scopus and Web of Science**
32
+
33
+ BibexPy is a Python-based software designed to streamline bibliometric data integration, deduplication, metadata enrichment, and format conversion. It simplifies the preparation of high-quality datasets for advanced analyses by automating traditionally manual and error-prone tasks.
34
+
35
+ ## Features
36
+
37
+ - **DOI-Based Deduplication and Merging**: Identifies and removes duplicate entries while enriching metadata by merging complementary records.
38
+ - **API-Driven Metadata Enrichment**: Completes missing fields such as abstracts, keywords, and affiliations using APIs like Unpaywall and Semantic Scholar.
39
+ - **Format Conversion**: Generates outputs compatible with VosViewer and Biblioshiny for easy analysis.
40
+ - **Command-Line Interface (CLI)**: Offers user-friendly interaction with minimal setup requirements.
41
+
42
+ # Prerequisites
43
+
44
+ ## Required Python Version
45
+ - Python ≥ 3.9.0
46
+
47
+ **Libraries**:
48
+ - `python-dotenv==1.0.0`
49
+ - `pandas>=2.0.0`
50
+ - `openpyxl>=3.1.2`
51
+ - `numpy>=1.24.0`
52
+ - `requests>=2.31.0`
53
+ - `scikit-learn>=1.3.0`
54
+ - `scipy>=1.11.0`
55
+ - `tqdm>=4.65.0`
56
+ - `xlrd>=2.0.1`
57
+ - `xlsxwriter>=3.1.0`
58
+ - `colorama >= 0.4.6`
59
+ - `typing-extensions >= 4.7.0`
60
+
61
+
62
+ ## Installation
63
+
64
+ 1. **Clone the Repository**
65
+ ```bash
66
+ git clone https://github.com/bcankara/BibexPy.git
67
+ ```
68
+ 2. **Navigate to the Directory**
69
+ ```bash
70
+ cd BibexPy
71
+ ```
72
+ 3. **Install Dependencies**
73
+ ```bash
74
+ pip install -r requirements.txt
75
+ ```
76
+
77
+ 4. **(Optional) Virtual Environment Setup**
78
+ ```bash
79
+ python -m venv venv
80
+ source venv/bin/activate # Mac/Linux
81
+ venv\Scripts\activate # Windows
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ 1. **Run the Application**
87
+ ```bash
88
+ python DataProcessor.py
89
+ ```
90
+
91
+ 2. **Follow the Workflow**
92
+ - Select your project.
93
+ - Upload Scopus (`.csv`) and Web of Science (`.txt`) files.
94
+ - Choose to enrich metadata (optional).
95
+ - Review and save the processed datasets.
96
+
97
+ ## Outputs
98
+
99
+ BibexPy generates the following outputs:
100
+ - Unified datasets (`Prefix_Bib.xlsx`).
101
+ - VosViewer-compatible files (`Prefix_Vos.txt`).
102
+ - Statistical summaries for dataset quality and completeness.
103
+
104
+ ## Documentation
105
+
106
+ For detailed documentation and examples, visit: [BibexPy Documentation](http://bibexpy.com/doc)
107
+
108
+ ## Support
109
+
110
+ For questions or feedback, contact:
111
+ 📧 **info@bibexpy.com**
112
+
113
+ ## License
114
+
115
+ BibexPy is licensed under the **GNU General Public License (GPL)**. See the LICENSE file for details.
116
+
117
+ ---
118
+
119
+ Enhance your bibliometric research with BibexPy, making data preparation efficient, reliable, and analysis-ready!
@@ -0,0 +1,10 @@
1
+ DataProcessor.py
2
+ LICENSE
3
+ README.md
4
+ setup.py
5
+ BibexPy.egg-info/PKG-INFO
6
+ BibexPy.egg-info/SOURCES.txt
7
+ BibexPy.egg-info/dependency_links.txt
8
+ BibexPy.egg-info/entry_points.txt
9
+ BibexPy.egg-info/requires.txt
10
+ BibexPy.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bibexpy = DataProcessor:main
@@ -0,0 +1,12 @@
1
+ python-dotenv==1.0.0
2
+ pandas>=2.0.0
3
+ openpyxl>=3.1.2
4
+ numpy>=1.24.0
5
+ requests>=2.31.0
6
+ scikit-learn>=1.3.0
7
+ scipy>=1.11.0
8
+ tqdm>=4.65.0
9
+ colorama>=0.4.6
10
+ xlrd>=2.0.1
11
+ xlsxwriter>=3.1.0
12
+ typing-extensions>=4.7.0
@@ -0,0 +1 @@
1
+ DataProcessor
@@ -0,0 +1,360 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import threading
5
+ import pandas as pd
6
+ from dotenv import load_dotenv
7
+ from Main.modules import (
8
+ ensure_dir,
9
+ find_files,
10
+ find_data_folders,
11
+ save_statistics,
12
+ save_comprehensive_statistics,
13
+ merge_databases_simple,
14
+ merge_databases_enhanced,
15
+ compare_merge_methods,
16
+ create_result_folder
17
+ )
18
+ from datetime import datetime
19
+
20
+ class ProcessIndicator:
21
+ """Process indicator for showing progress"""
22
+ busy = False
23
+ delay = 0.1
24
+
25
+ @staticmethod
26
+ def indicator():
27
+ while 1:
28
+ for cursor in '|/-\\':
29
+ yield cursor
30
+
31
+ def __init__(self, description="Processing"):
32
+ self.description = description
33
+ self.generator = self.indicator()
34
+ self.busy = False
35
+ self.visible = False
36
+
37
+ def write_next(self):
38
+ with self._screen_lock:
39
+ if not self.visible:
40
+ sys.stdout.write(f"\r{self.description:<50}")
41
+ self.visible = True
42
+ sys.stdout.write(next(self.generator))
43
+ sys.stdout.flush()
44
+ sys.stdout.write('\b')
45
+
46
+ def remove_indicator(self, cleanup=False):
47
+ with self._screen_lock:
48
+ if self.visible:
49
+ sys.stdout.write('\b')
50
+ self.visible = False
51
+ if cleanup:
52
+ sys.stdout.write(f"\r{self.description:<50}[Completed]\n")
53
+ sys.stdout.flush()
54
+
55
+ def indicator_task(self):
56
+ while self.busy:
57
+ self.write_next()
58
+ time.sleep(self.delay)
59
+
60
+ def __enter__(self):
61
+ if sys.stdout.isatty():
62
+ self._screen_lock = threading.Lock()
63
+ self.busy = True
64
+ self.thread = threading.Thread(target=self.indicator_task)
65
+ self.thread.start()
66
+
67
+ def __exit__(self, exc_type, exc_val, exc_tb):
68
+ if sys.stdout.isatty():
69
+ self.busy = False
70
+ time.sleep(self.delay)
71
+ self.remove_indicator(cleanup=True)
72
+
73
+ def process_wos_data(input_file: str, output_file: str) -> tuple[bool, pd.DataFrame, dict]:
74
+ """Process WoS data"""
75
+ try:
76
+ from Main.wos2xlsx import save_to_excel
77
+ success = save_to_excel(input_file, output_file)
78
+
79
+ if success:
80
+ with ProcessIndicator("Converting WoS data to Excel format") as indicator:
81
+ df = pd.read_excel(output_file)
82
+ stats = {
83
+ 'Record Count': len(df),
84
+ 'Column Count': len(df.columns),
85
+ 'Non-Empty Columns': df.count().to_dict()
86
+ }
87
+ return True, df, stats
88
+ return False, None, {}
89
+ except Exception as e:
90
+ sys.stderr.write(f"\nError: {str(e)}\n")
91
+ return False, None, {}
92
+
93
+ def process_scopus_data(input_file: str, output_file: str) -> tuple[bool, pd.DataFrame, dict]:
94
+ """Process Scopus data"""
95
+ try:
96
+ from Main.scp2xlsx import save_to_excel
97
+ success = save_to_excel(input_file, output_file)
98
+
99
+ if success:
100
+ with ProcessIndicator("Converting Scopus data to Excel format") as indicator:
101
+ df = pd.read_excel(output_file)
102
+ stats = {
103
+ 'Record Count': len(df),
104
+ 'Column Count': len(df.columns),
105
+ 'Non-Empty Columns': df.count().to_dict()
106
+ }
107
+ return True, df, stats
108
+ return False, None, {}
109
+ except Exception as e:
110
+ sys.stderr.write(f"\nError: {str(e)}\n")
111
+ return False, None, {}
112
+
113
+ def merge_txt_files(data_dir: str) -> str:
114
+ """Merge all txt files in data directory into wos_raw.txt in merged_raw folder"""
115
+ txt_files = find_files(data_dir, "txt")
116
+ if not txt_files:
117
+ raise ValueError("No WoS files (txt) found in Data folder.")
118
+
119
+ # Create merged_raw directory if not exists
120
+ merged_raw_dir = os.path.join(data_dir, "merged_raw")
121
+ os.makedirs(merged_raw_dir, exist_ok=True)
122
+
123
+ output_file = os.path.join(merged_raw_dir, "wos_raw.txt")
124
+ with open(output_file, 'w', encoding='utf-8') as outfile:
125
+ for txt_file in txt_files:
126
+ with open(txt_file, 'r', encoding='utf-8') as infile:
127
+ outfile.write(infile.read())
128
+ outfile.write('\n') # Add newline between files
129
+
130
+ return output_file
131
+
132
+ def merge_csv_files(data_dir: str) -> str:
133
+ """Merge all csv files in data directory into scp_raw.csv in merged_raw folder"""
134
+ csv_files = find_files(data_dir, "csv")
135
+ if not csv_files:
136
+ raise ValueError("No Scopus files (csv) found in Data folder.")
137
+
138
+ # Create merged_raw directory if not exists
139
+ merged_raw_dir = os.path.join(data_dir, "merged_raw")
140
+ os.makedirs(merged_raw_dir, exist_ok=True)
141
+
142
+ output_file = os.path.join(merged_raw_dir, "scp_raw.csv")
143
+
144
+ if len(csv_files) == 1:
145
+ # If only one CSV file, just copy it
146
+ import shutil
147
+ shutil.copy2(csv_files[0], output_file)
148
+ else:
149
+ # Read and combine all CSV files
150
+ all_data = []
151
+ for csv_file in csv_files:
152
+ df = pd.read_csv(csv_file, encoding='utf-8')
153
+ all_data.append(df)
154
+
155
+ # Concatenate all dataframes
156
+ combined_df = pd.concat(all_data, ignore_index=True)
157
+
158
+ # Save combined data
159
+ combined_df.to_csv(output_file, index=False, encoding='utf-8')
160
+
161
+ return output_file
162
+
163
+ def main():
164
+ try:
165
+ # Load environment variables
166
+ load_dotenv()
167
+
168
+ print("Database Merge Tool")
169
+ print("------------------")
170
+
171
+ # Find project folders
172
+ workspace_dir = "Workspace"
173
+ if not os.path.exists(workspace_dir):
174
+ os.makedirs(workspace_dir)
175
+
176
+ project_folders = [d for d in os.listdir(workspace_dir) if os.path.isdir(os.path.join(workspace_dir, d))]
177
+
178
+ if not project_folders:
179
+ sys.stderr.write("\nError: No project folder found in Workspace.\n")
180
+ return
181
+
182
+ print("\nProject Folders:")
183
+ for i, folder in enumerate(project_folders, 1):
184
+ print(f"{i}. {folder}")
185
+
186
+ # Project selection
187
+ while True:
188
+ try:
189
+ choice = int(input("\nSelect project number: "))
190
+ if 1 <= choice <= len(project_folders):
191
+ project_dir = os.path.join(workspace_dir, project_folders[choice-1])
192
+ break
193
+ else:
194
+ sys.stderr.write("Error: Invalid selection.\n")
195
+ except ValueError:
196
+ sys.stderr.write("Error: Invalid input. Please enter a number.\n")
197
+
198
+ # Create Data directory if not exists
199
+ data_dir = os.path.join(project_dir, "Data")
200
+ if not os.path.exists(data_dir):
201
+ os.makedirs(data_dir)
202
+
203
+ print(f"\nSelected Project: {project_folders[choice-1]}")
204
+
205
+ # Create unique analysis directory
206
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
207
+ analysis_dir = os.path.join(project_dir, f"Analysis_{timestamp}")
208
+ os.makedirs(analysis_dir, exist_ok=True)
209
+
210
+ # Create subfolders
211
+ text_files_dir = os.path.join(analysis_dir, "Text_Files")
212
+ cell_files_dir = os.path.join(analysis_dir, "Cell_Files")
213
+ os.makedirs(text_files_dir, exist_ok=True)
214
+ os.makedirs(cell_files_dir, exist_ok=True)
215
+
216
+ # Set output files in analysis directory
217
+ wos_output = os.path.join(cell_files_dir, "WoS.xlsx")
218
+ scopus_output = os.path.join(cell_files_dir, "Scopus.xlsx")
219
+ merged_bib = os.path.join(cell_files_dir, "Merged_Bib.xlsx")
220
+ merged_vos = os.path.join(text_files_dir, "Merged_Vos.txt")
221
+ api_enriched_bib = os.path.join(cell_files_dir, "Merged_API_Enriched_Bib.xlsx")
222
+ api_enriched_vos = os.path.join(text_files_dir, "Merged_API_Enriched_Vos.txt")
223
+ stats_excel = os.path.join(analysis_dir, "Statistics.xlsx")
224
+ api_log = os.path.join(analysis_dir, "Api_Log.txt")
225
+ api_updates = os.path.join(analysis_dir, "Api_Update.xlsx")
226
+
227
+ # Check if merged_raw directory exists and contains required files
228
+ merged_raw_dir = os.path.join(data_dir, "merged_raw")
229
+ wos_raw = os.path.join(merged_raw_dir, "wos_raw.txt")
230
+ scp_raw = os.path.join(merged_raw_dir, "scp_raw.csv")
231
+
232
+ use_existing = False
233
+ if os.path.exists(merged_raw_dir) and os.path.exists(wos_raw) and os.path.exists(scp_raw):
234
+ while True:
235
+ choice = input("\nPreviously merged raw files found. What would you like to do?\n"
236
+ "1. Use previously merged files\n"
237
+ "2. Perform new merge\n"
238
+ "Your choice (1/2): ")
239
+ if choice in ['1', '2']:
240
+ use_existing = (choice == '1')
241
+ break
242
+ else:
243
+ sys.stderr.write("Invalid choice. Please enter 1 or 2.\n")
244
+
245
+ if not use_existing:
246
+ if os.path.exists(merged_raw_dir):
247
+ print("\nDeleting old merged files...")
248
+ import shutil
249
+ shutil.rmtree(merged_raw_dir)
250
+
251
+ # Merge txt files into wos_raw.txt
252
+ print("\nMerging process in progress...")
253
+ try:
254
+ wos_input = merge_txt_files(data_dir)
255
+ print(f"WoS files merged: {os.path.basename(wos_input)}")
256
+ except ValueError as e:
257
+ sys.stderr.write(f"\nError: {str(e)}\n")
258
+ return
259
+
260
+ # Merge csv files into scp_raw.csv
261
+ print("\nMerging Scopus files...")
262
+ try:
263
+ scopus_input = merge_csv_files(data_dir)
264
+ print(f"Scopus files merged: {os.path.basename(scopus_input)}")
265
+ except ValueError as e:
266
+ sys.stderr.write(f"\nError: {str(e)}\n")
267
+ return
268
+ else:
269
+ print("\nUsing existing merged files...")
270
+ wos_input = wos_raw
271
+ scopus_input = scp_raw
272
+
273
+ # Process WoS data
274
+ print("\nProcessing WoS data...")
275
+ wos_success, wos_df, wos_stats = process_wos_data(wos_input, wos_output)
276
+ if not wos_success:
277
+ sys.stderr.write("\nError: Failed to process WoS data.\n")
278
+ return
279
+ print("WoS data processing completed.")
280
+
281
+ # Process Scopus data
282
+ print("\nProcessing Scopus data...")
283
+ scopus_success, scopus_df, scopus_stats = process_scopus_data(scopus_input, scopus_output)
284
+ if not scopus_success:
285
+ sys.stderr.write("\nError: Failed to process Scopus data.\n")
286
+ return
287
+ print("Scopus data processing completed.")
288
+
289
+ # Get Scopus API key from environment variable
290
+ scopus_api_key = os.getenv('SCOPUS_API_KEY')
291
+ unpaywall_email = os.getenv('UNPAYWALL_EMAIL')
292
+
293
+ if not scopus_api_key:
294
+ print("\nWarning: Scopus API key not found in .env file.")
295
+ print("You can add it to the .env file to enable Scopus metadata enrichment.")
296
+ print("Continuing with other data sources...")
297
+
298
+ if not unpaywall_email:
299
+ print("\nWarning: Unpaywall email not found in .env file.")
300
+ print("You can add it to the .env file to enable Unpaywall metadata enrichment.")
301
+ print("Continuing with other data sources...")
302
+
303
+ # Enhanced merge
304
+ print("\nMerging process in progress...")
305
+ enhanced_success, enhanced_stats, enhanced_df = merge_databases_enhanced(
306
+ wos_df,
307
+ scopus_df,
308
+ merged_bib,
309
+ scopus_api_key=scopus_api_key,
310
+ unpaywall_email=unpaywall_email,
311
+ result_dir=analysis_dir
312
+ )
313
+
314
+ print("\nConverting data to VosViewer format...")
315
+ from Main.xlsx2vos import convert_excel_to_wos
316
+ convert_excel_to_wos(merged_bib, merged_vos)
317
+ print("VosViewer conversion completed.")
318
+
319
+ if enhanced_success:
320
+ # Save statistics
321
+ print("\nPreparing statistics...")
322
+ all_stats = {
323
+ 'WoS Statistics': wos_stats,
324
+ 'Scopus Statistics': scopus_stats,
325
+ 'Merge Statistics': enhanced_stats
326
+ }
327
+
328
+ save_comprehensive_statistics(all_stats, wos_df, scopus_df, enhanced_df, stats_excel)
329
+ print("Statistics completed.")
330
+
331
+ # Check and convert API enriched file
332
+ if os.path.exists(api_enriched_bib):
333
+ print("\nConverting API Enriched data to VosViewer format...")
334
+ convert_excel_to_wos(api_enriched_bib, api_enriched_vos)
335
+ print("API Enriched VosViewer conversion completed.")
336
+
337
+ print("\nProcess completed successfully.")
338
+ print(f"\nAnalysis results saved to: {analysis_dir}")
339
+ print("Generated Files:")
340
+ print(f"1. WoS Data: {os.path.basename(wos_output)}")
341
+ print(f"2. Scopus Data: {os.path.basename(scopus_output)}")
342
+ print(f"3. Merged Data (Biblioshiny): {os.path.basename(merged_bib)}")
343
+ print(f"4. Merged Data (VosViewer): {os.path.basename(merged_vos)}")
344
+ print(f"5. Statistics: {os.path.basename(stats_excel)}")
345
+ print(f"6. API Log: {os.path.basename(api_log)}")
346
+ if os.path.exists(api_updates):
347
+ print(f"7. API Updates: {os.path.basename(api_updates)}")
348
+ if os.path.exists(api_enriched_bib):
349
+ print(f"8. API Enriched Data (Biblioshiny): {os.path.basename(api_enriched_bib)}")
350
+ print(f"9. API Enriched Data (VosViewer): {os.path.basename(api_enriched_vos)}")
351
+ else:
352
+ sys.stderr.write("\nError: Merge process failed.\n")
353
+
354
+ except Exception as e:
355
+ print(f"\nAn unexpected error occurred: {str(e)}")
356
+ finally:
357
+ print("\nProgram terminating...")
358
+
359
+ if __name__ == "__main__":
360
+ main()