oracle2ssrs 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.
Files changed (88) hide show
  1. oracle2ssrs-1.0.0/.gitignore +74 -0
  2. oracle2ssrs-1.0.0/LICENSE +21 -0
  3. oracle2ssrs-1.0.0/PKG-INFO +425 -0
  4. oracle2ssrs-1.0.0/README.md +397 -0
  5. oracle2ssrs-1.0.0/backend/app.py +1841 -0
  6. oracle2ssrs-1.0.0/backend/cli.py +342 -0
  7. oracle2ssrs-1.0.0/backend/converter/__init__.py +899 -0
  8. oracle2ssrs-1.0.0/backend/converter/artifact_enrich.py +481 -0
  9. oracle2ssrs-1.0.0/backend/converter/audit.py +417 -0
  10. oracle2ssrs-1.0.0/backend/converter/batch.py +305 -0
  11. oracle2ssrs-1.0.0/backend/converter/bundle_export.py +252 -0
  12. oracle2ssrs-1.0.0/backend/converter/burst_driver.ps1.txt +446 -0
  13. oracle2ssrs-1.0.0/backend/converter/burst_pack.py +840 -0
  14. oracle2ssrs-1.0.0/backend/converter/bursting.py +855 -0
  15. oracle2ssrs-1.0.0/backend/converter/compare.py +215 -0
  16. oracle2ssrs-1.0.0/backend/converter/cross_validate.py +316 -0
  17. oracle2ssrs-1.0.0/backend/converter/deployment.py +339 -0
  18. oracle2ssrs-1.0.0/backend/converter/fidelity.py +306 -0
  19. oracle2ssrs-1.0.0/backend/converter/generators/__init__.py +0 -0
  20. oracle2ssrs-1.0.0/backend/converter/generators/rdl.py +32657 -0
  21. oracle2ssrs-1.0.0/backend/converter/ingest.py +607 -0
  22. oracle2ssrs-1.0.0/backend/converter/licensing.py +55 -0
  23. oracle2ssrs-1.0.0/backend/converter/models.py +557 -0
  24. oracle2ssrs-1.0.0/backend/converter/parsers/__init__.py +0 -0
  25. oracle2ssrs-1.0.0/backend/converter/parsers/oracle_colors.py +249 -0
  26. oracle2ssrs-1.0.0/backend/converter/parsers/oracle_xml.py +2685 -0
  27. oracle2ssrs-1.0.0/backend/converter/preview/__init__.py +0 -0
  28. oracle2ssrs-1.0.0/backend/converter/preview/html_mockup.py +6037 -0
  29. oracle2ssrs-1.0.0/backend/converter/preview/live_data.py +462 -0
  30. oracle2ssrs-1.0.0/backend/converter/preview/mockup_variants.py +187 -0
  31. oracle2ssrs-1.0.0/backend/converter/rdl_postprocess.py +344 -0
  32. oracle2ssrs-1.0.0/backend/converter/subreports.py +1694 -0
  33. oracle2ssrs-1.0.0/backend/converter/translators/__init__.py +0 -0
  34. oracle2ssrs-1.0.0/backend/converter/translators/format_exception.py +401 -0
  35. oracle2ssrs-1.0.0/backend/converter/translators/plsql_formula.py +1220 -0
  36. oracle2ssrs-1.0.0/backend/converter/translators/plsql_to_tsql.py +1287 -0
  37. oracle2ssrs-1.0.0/backend/converter/translators/udf_stubs.py +388 -0
  38. oracle2ssrs-1.0.0/backend/converter/validators/__init__.py +0 -0
  39. oracle2ssrs-1.0.0/backend/converter/validators/coverage.py +146 -0
  40. oracle2ssrs-1.0.0/backend/converter/validators/layout_audit.py +119 -0
  41. oracle2ssrs-1.0.0/backend/converter/validators/mockup_audit.py +203 -0
  42. oracle2ssrs-1.0.0/backend/converter/validators/no_prompt_gate.py +564 -0
  43. oracle2ssrs-1.0.0/backend/converter/validators/preflight.py +1340 -0
  44. oracle2ssrs-1.0.0/backend/converter/validators/publish_semantics.py +1267 -0
  45. oracle2ssrs-1.0.0/backend/converter/validators/rdl_check.py +537 -0
  46. oracle2ssrs-1.0.0/backend/converter/validators/sql_syntax.py +200 -0
  47. oracle2ssrs-1.0.0/backend/converter/validators/tsql_check.py +515 -0
  48. oracle2ssrs-1.0.0/backend/converter/validators/vb_expr_check.py +157 -0
  49. oracle2ssrs-1.0.0/backend/converter/validators/vb_expr_eval.py +780 -0
  50. oracle2ssrs-1.0.0/backend/db/sample.sqlite +0 -0
  51. oracle2ssrs-1.0.0/backend/db/seed_sample_db.py +237 -0
  52. oracle2ssrs-1.0.0/frontend/static/css/style.css +1344 -0
  53. oracle2ssrs-1.0.0/frontend/static/js/app.js +4929 -0
  54. oracle2ssrs-1.0.0/frontend/static/js/compare_mode.js +397 -0
  55. oracle2ssrs-1.0.0/frontend/static/js/demo_mode.js +405 -0
  56. oracle2ssrs-1.0.0/frontend/static/js/highlight.js +208 -0
  57. oracle2ssrs-1.0.0/frontend/static/js/scroll_regions.js +180 -0
  58. oracle2ssrs-1.0.0/frontend/static/js/theme.js +136 -0
  59. oracle2ssrs-1.0.0/frontend/templates/index.html +725 -0
  60. oracle2ssrs-1.0.0/oracle2ssrs/__init__.py +22 -0
  61. oracle2ssrs-1.0.0/oracle2ssrs/__main__.py +44 -0
  62. oracle2ssrs-1.0.0/pyproject.toml +70 -0
  63. oracle2ssrs-1.0.0/samples/oracle/SAMPLE_FEE_NOTICE.xml +165 -0
  64. oracle2ssrs-1.0.0/samples/oracle/SAMPLE_INSPECTION.xml +175 -0
  65. oracle2ssrs-1.0.0/tools/renderlab/README.md +63 -0
  66. oracle2ssrs-1.0.0/tools/renderlab/RenderLab.cs +100 -0
  67. oracle2ssrs-1.0.0/tools/renderlab/RenderLab.exe +0 -0
  68. oracle2ssrs-1.0.0/tools/renderlab/RenderLab.exe.config +14 -0
  69. oracle2ssrs-1.0.0/tools/renderlab/blank_measure.py +2560 -0
  70. oracle2ssrs-1.0.0/tools/renderlab/chart_ink.py +215 -0
  71. oracle2ssrs-1.0.0/tools/renderlab/fetch_reportviewer.py +123 -0
  72. oracle2ssrs-1.0.0/tools/renderlab/html_to_png.py +44 -0
  73. oracle2ssrs-1.0.0/tools/renderlab/ms_layout.py +660 -0
  74. oracle2ssrs-1.0.0/tools/renderlab/rdl_preview.py +95 -0
  75. oracle2ssrs-1.0.0/tools/renderlab/render.py +245 -0
  76. oracle2ssrs-1.0.0/tools/renderlab/render_expr.ps1 +187 -0
  77. oracle2ssrs-1.0.0/tools/renderlab/render_overlap.py +368 -0
  78. oracle2ssrs-1.0.0/tools/renderlab/render_rdl.ps1 +138 -0
  79. oracle2ssrs-1.0.0/tools/renderlab/run_corpus.py +129 -0
  80. oracle2ssrs-1.0.0/tools/renderlab/shape_matrix.py +174 -0
  81. oracle2ssrs-1.0.0/tools/renderlab/vb_expr_check.ps1 +407 -0
  82. oracle2ssrs-1.0.0/tools/renderlab/vb_expr_eval.ps1 +682 -0
  83. oracle2ssrs-1.0.0/tools/renderlab/vb_invoke.ps1 +99 -0
  84. oracle2ssrs-1.0.0/tools/renderlab/vb_invoke.py +66 -0
  85. oracle2ssrs-1.0.0/tools/ssrscheck/README.md +69 -0
  86. oracle2ssrs-1.0.0/tools/ssrscheck/server_render.ps1 +122 -0
  87. oracle2ssrs-1.0.0/tools/ssrscheck/smoke_deploy.ps1 +101 -0
  88. oracle2ssrs-1.0.0/tools/ssrscheck/smoke_deploy.py +221 -0
@@ -0,0 +1,74 @@
1
+ # Secrets / credentials -- NEVER commit these (the repo is public)
2
+ .env
3
+ *.env
4
+ !.env.example
5
+ backend/.env
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+ *.so
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ .venv/
29
+ venv/
30
+ ENV/
31
+ env/
32
+ .pytest_cache/
33
+ pytest-cache-files-*/
34
+
35
+ # Editor / IDE
36
+ .vscode/
37
+ .idea/
38
+ *.swp
39
+ *.swo
40
+ .DS_Store
41
+ Thumbs.db
42
+
43
+ # OS / mount artifacts that bit us all session
44
+ *.sqlite-journal
45
+ *.sqlite-wal
46
+ *.sqlite-shm
47
+
48
+ # Logs
49
+ *.log
50
+ flask.log
51
+
52
+ # Don't commit user-uploaded artifacts (only the bundled samples)
53
+ backend/uploads/
54
+
55
+ # Local Flask runtime
56
+ instance/
57
+
58
+ # Optional: ignore generated RDL outputs (keep the canonical sample)
59
+ samples/expected_rdl/*.rdl
60
+ !samples/expected_rdl/SAMPLE_INSPECTION.rdl
61
+
62
+ # RenderLab: fetched Microsoft DLLs + rendered verification output
63
+ tools/renderlab/lib/
64
+ tools/renderlab/out/
65
+
66
+ # Local scratch runner (never commit)
67
+ _scratch_run.py
68
+
69
+ # Private pre-push watchlist (tools/preflight_push.py) -- contains the very
70
+ # terms that must never appear in this public repo, so it NEVER ships.
71
+ .canary_local
72
+
73
+ # Local editor/assistant tooling (per-machine, never ships)
74
+ .claude/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Max Casteel (github.com/Maximilian741)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,425 @@
1
+ Metadata-Version: 2.5
2
+ Name: oracle2ssrs
3
+ Version: 1.0.0
4
+ Summary: Convert Oracle Reports XML exports into SSRS RDL reports.
5
+ Project-URL: Homepage, https://github.com/ahmedmgh67/OFR2SSRS
6
+ Project-URL: Source, https://github.com/ahmedmgh67/OFR2SSRS
7
+ Author: Max Casteel
8
+ Maintainer: Ahmed Gamal
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: oracle-reports,rdl,report-migration,ssrs
12
+ Classifier: Framework :: Flask
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Database
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: flask>=3.1.3
18
+ Requires-Dist: lxml>=6.1.3
19
+ Requires-Dist: pymupdf<2,>=1.28.2
20
+ Requires-Dist: pypdf>=6.21.1
21
+ Requires-Dist: python-docx>=1.2.0
22
+ Requires-Dist: python-dotenv>=1.2.4
23
+ Requires-Dist: waitress>=3.0.2
24
+ Requires-Dist: werkzeug>=3.1.9
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=9.1.1; extra == 'test'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Oracle Reports to SSRS Converter
30
+
31
+ **License: [MIT](LICENSE)** — free to use, modify, distribute and build on,
32
+ including commercial and government use, with no fee and no lock-in.
33
+
34
+ Drag an Oracle Reports artifact in, get a deployable SSRS RDL out.
35
+
36
+ Oracle Reports is desupported. Oracle2SSRS converts your reports to SSRS in
37
+ minutes — and **proves** the output instead of promising it:
38
+
39
+ - **791 automated tests**, including renders through **Microsoft's own
40
+ ReportViewer engine** (the same RDL processing code SSRS runs) with the
41
+ produced PDFs measured for page cadence, blank pages, and geometry.
42
+ - **Generated RDL validated** against Microsoft's official RDL 2008 XSD
43
+ schema (`ReportDefinition_2008.xsd`), loaded directly in the test suite.
44
+ - **Never prompts for parameter values** — at upload, Refresh Fields, or
45
+ run. Every parameter and query bind ships wired.
46
+ - **Batch a whole folder** and get a Migration Assessment: per-report
47
+ effort tiers, fidelity scores, and engine-render verdicts.
48
+
49
+ ![Oracle2SSRS — drag-and-drop Oracle Reports to SSRS conversion](docs/screenshots/hero-app-view.png)
50
+
51
+ ## What it does
52
+
53
+ Oracle2SSRS is a local Flask app that ingests Oracle Reports artifacts —
54
+ Oracle Reports XML, raw `.sql`, `.docx` walkthroughs, `.rdf` exports, and
55
+ PNG/JPG reference screenshots, including whole folders of mixed files — and
56
+ emits a structurally valid SSRS 2008+ `.rdl` you can upload to a real Report
57
+ Server. It translates Oracle SQL and PL/SQL formula logic into computable
58
+ SSRS expressions, reproduces the original paper-layout geometry, and wires
59
+ every parameter so the report runs without prompting.
60
+
61
+ A four-pane preview lets you verify the conversion before you deploy: the
62
+ rendered HTML mockup, the raw RDL, bursting (per-recipient distribution)
63
+ detection, and sub-report (drill-through) child generation.
64
+
65
+ ## Proves, not promises
66
+
67
+ Most converters stop at "the XML parsed." Oracle2SSRS holds its output to
68
+ two independent standards that catch different classes of defect:
69
+
70
+ - **Schema-valid RDL.** Every generated `.rdl` is validated against
71
+ Microsoft's official `ReportDefinition_2008.xsd` using `lxml`'s
72
+ `XMLSchema`. This is enforced across the test suite, so structurally
73
+ invalid RDL cannot ship.
74
+ - **Engine-rendered RDL.** XSD validation proves the document is
75
+ well-formed; it cannot prove the report lays out correctly. So
76
+ `tools/renderlab` drives Microsoft's ReportViewer `LocalReport` engine —
77
+ the same RDL processing code SSRS runs — to render real PDFs from
78
+ generated RDLs with synthetic data, then measures them for page cadence,
79
+ blank pages, and geometry. This catches publish-time semantic rules that
80
+ XSD validation cannot (for example aggregate-in-`Lookup`, and
81
+ `TablixMember` `RepeatOnNewPage` consistency).
82
+ - **Compile-verified expressions.** A schema-valid RDL that renders can still
83
+ carry a *VB.NET expression* that throws `#Error` the moment the report
84
+ server evaluates it. So every generated `=...` expression — and the report's
85
+ own `<Code>` block — is compiled through the real `System.CodeDom`
86
+ `VBCodeProvider`, the same compilation SSRS performs at publish time. A bad
87
+ `IIf` arity, a trailing comma, an undefined function, or a leaked Oracle `||`
88
+ is caught here, before download, instead of in your live report. (Runs on
89
+ Windows with the .NET Framework; degrades to a clean skip elsewhere.)
90
+
91
+ Run `python tools/renderlab/fetch_reportviewer.py` once to fetch the
92
+ official Microsoft runtime from nuget.org; the rendering checks then run as
93
+ part of `pytest`. They degrade gracefully when the engine host is
94
+ unavailable.
95
+
96
+ ## Quick start
97
+
98
+ ### Install with pip
99
+
100
+ ```bash
101
+ pip install oracle2ssrs
102
+
103
+ oracle2ssrs # web app at http://127.0.0.1:5057
104
+ oracle2ssrs-convert reports/ --out out # batch-convert without the UI
105
+ ```
106
+
107
+ `oracle2ssrs --host 0.0.0.0 --port 8080` serves other machines on another
108
+ port. Settings (`O2S_*`) are read from a `.env` file in the folder you
109
+ start it from. Requires Python 3.10+.
110
+
111
+ ### Run from source
112
+
113
+ ```bash
114
+ git clone https://github.com/Maximilian741/OFR2SSRS.git
115
+ cd OFR2SSRS
116
+
117
+ # 1. ONE-TIME: install the Python dependencies (the launcher never does this)
118
+ python -m pip install -r requirements.txt
119
+
120
+ # 2. Start the app
121
+ run.bat # Windows
122
+ ./run.sh # macOS / Linux / WSL / Git Bash
123
+ ```
124
+
125
+ The launcher only **starts** the app — it deliberately never installs
126
+ anything (on locked-down machines an implicit `pip install` triggers install
127
+ activity the user may be unable to act on). If step 1 was skipped, the
128
+ launcher stops and prints the exact command instead of a Python traceback:
129
+
130
+ ```text
131
+ ModuleNotFoundError: No module named 'flask' <- what you see without step 1
132
+ python -m pip install -r requirements.txt <- the one-time fix
133
+ ```
134
+
135
+ Then open <http://127.0.0.1:5057> and drop an Oracle Reports artifact on the
136
+ page. Override the listen port with `PORT=8080 ./run.sh`. Requires
137
+ Python 3.10+.
138
+
139
+ To install into a virtualenv instead:
140
+
141
+ ```bash
142
+ python -m venv .venv
143
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
144
+ pip install -r requirements.txt
145
+ ```
146
+
147
+ ## How it works — the four-pane UI
148
+
149
+ Every conversion surfaces **four main tabs**:
150
+
151
+ - **Preview** — what the report actually prints. The *frontend* mode
152
+ renders the generated RDL through **Microsoft's own ReportViewer
153
+ engine** and shows the resulting pages (an instant HTML mockup stands
154
+ in while the engine works, then the true pages swap in); a Sample-rows
155
+ control re-renders at any sample size. The *backend* mode shows the
156
+ Report-Builder skeleton with field-name placeholders. On machines
157
+ without the engine the preview degrades gracefully (rendered PDF, then
158
+ mockup) and always says which renderer produced what you see.
159
+ - **RDL XML** — syntax-highlighted, structurally valid SSRS RDL. Download it
160
+ and upload straight to your Report Server.
161
+ - **Bursting** — automatic detection of Oracle distribution patterns
162
+ (one output file per record, file-name templates, mail destinations) with
163
+ a downloadable **Burst Pack**: the report with a hidden per-key filter
164
+ parameter, a key-list report, and a zero-install PowerShell driver that
165
+ renders one file per key through SSRS URL access on any edition.
166
+ - **Sub-Reports** — when the parser detects drill-through child reports, this
167
+ tab lets you upload the child Oracle XML/SQL/DOCX and generates an RDL for
168
+ each child.
169
+
170
+ Advanced views (side-by-side diff, validation, deploy checklist, and an
171
+ Extras panel with the fidelity report and audit trail) are one click away behind
172
+ an **Advanced views** toggle.
173
+
174
+ ## Feature highlights
175
+
176
+ ### PL/SQL formula translation
177
+
178
+ The core feature: a real deterministic compiler — a tokenizer plus a
179
+ precedence-climbing parser — not pattern substitution. It compiles an Oracle
180
+ `CF_`/`CP_` formula body into an SSRS VB.NET expression that computes inline.
181
+
182
+ - **Body reduction** folds `IF`/`ELSIF`/`ELSE` and local `var := expr`
183
+ assignments into a single effective return expression (nested `CASE`).
184
+ - **Operator and function mappings** include `||` → `&`, `NVL`/`NVL2`/
185
+ `COALESCE` → `IIf(IsNothing(...))`, `DECODE` → nested `IIf`, searched
186
+ `CASE` → nested `IIf`, plus `SUBSTR`→`Mid`, `INSTR`→`InStr`,
187
+ `LENGTH`→`Len`, `UPPER`/`LOWER`→`UCase`/`LCase`, `REPLACE`/`LPAD`/`RPAD`/
188
+ `TRIM`/`INITCAP`, `TO_CHAR`→`Format` (with Oracle→.NET mask translation),
189
+ `TO_NUMBER`→`CDbl`, `TO_DATE`→`CDate`, `ROUND`/`TRUNC`/`FLOOR`/`CEIL`/
190
+ `ABS`/`POWER`/`MOD`/`SIGN`/`GREATEST`/`LEAST`, `SYSDATE`→`Now()`, and
191
+ `CHR(10/13/9)`→`vbLf`/`vbCr`/`vbTab`.
192
+ - **Conditional `CP_` placeholders** (side-effect `:CP_X := expr` outputs,
193
+ including build-up and branch assignments) are recovered into a `CASE`.
194
+ - **Correlated cross-dataset counts** compile to
195
+ `=LookupSet(key, key, Fields!result.Value, "child").Length`.
196
+ - **Honest fallback.** A formula is reported as translated only when the
197
+ *whole* expression compiles with no unknown calls. An external package
198
+ function is recorded as `unresolved`, and when a formula cannot be
199
+ computed the resolver strips the `CF_`/`CP_`/`CS_`/`CG_`/`CT_` prefix and
200
+ binds to a same-stem real column (via `Lookup` if cross-dataset) — so a
201
+ broken expression never reaches SSRS.
202
+
203
+ The companion SQL translation has a rule-based core (`DECODE`, `NVL`,
204
+ `TO_CHAR`, `TO_DATE`, `TRUNC`, `SYSDATE`, `INSTR`, `SUBSTR`, `CHR`, `||`,
205
+ `(+)`, `LISTAGG`, `ROWNUM`, bind variables, lexical references) and supports
206
+ two SQL targets (see below).
207
+
208
+ ### Parameter filters that actually filter
209
+
210
+ Oracle reports often build their `WHERE` clause from a **lexical parameter** —
211
+ a chunk of SQL *text* (`&P_CRITERIA`) that a `BEFORE REPORT` trigger assembles
212
+ at run time from the user's prompts (year, date range, name). SSRS has no
213
+ lexical parameter, so a naive conversion drops that text and the report runs
214
+ **unfiltered** — the prompts render but do nothing.
215
+
216
+ Oracle2SSRS recognizes the standard "criteria builder" idiom (a trigger
217
+ concatenating `F_Criteria_*_Bind(column, 'P_param')` fragments against
218
+ `cv*` column constants) and **reconstructs it into real, `NULL`-safe,
219
+ parameter-bound predicates**:
220
+
221
+ ```sql
222
+ AND (:P_Year IS NULL OR TO_CHAR(O.Order_Date,'YYYY') = UPPER(TRIM(:P_Year)))
223
+ AND (:P_Date_From IS NULL OR TRUNC(O.Status_Date) >= :P_Date_From)
224
+ ```
225
+
226
+ Each declared filter parameter is bound as a real query parameter, so picking a
227
+ value filters and leaving it blank widens to all rows — exactly the Oracle
228
+ behavior. Only predicates whose column resolves to a known constant are
229
+ emitted; anything the tool can't reconstruct with confidence is left with an
230
+ honest placeholder rather than a wrong filter.
231
+
232
+ ### Layout fidelity
233
+
234
+ - **Positional document rendering** — Oracle paper-layout geometry
235
+ (Top/Left/Width/Height) drives the SSRS body. Single-record forms route to
236
+ a positioned per-record body; tabular sources get a grid. The generator
237
+ picks the right `<Body>` shape for the source's frame structure instead of
238
+ forcing every report into one mold.
239
+ - **Per-segment mixed fonts** — a mixed-font Oracle text field (for example
240
+ an unbold caption next to a bold value) is parsed into runs and emitted as
241
+ one paragraph per segment, each keeping its own weight, size, italic,
242
+ underline, and color; it collapses to the uniform path when the segments
243
+ do not actually differ.
244
+ - **Embedded seals / logos / watermarks** — images stored in the Oracle
245
+ export's `binaryData` (both inline and document-level styles, including a
246
+ nibble-swapped hex encoding some exports use) are decoded, MIME-sniffed,
247
+ and emitted as RDL `<EmbeddedImages>` and into the mockup. Layout
248
+ placeholders without bytes get an upload slot in the sidebar, previewed
249
+ live in the mockup.
250
+ - **White-border-over-watermark fix** — a white border on a white or
251
+ transparent textbox is invisible on the page but paints over a seal; the
252
+ generator suppresses it (`BorderStyle=None`) in that case while keeping a
253
+ real white gridline over a colored background.
254
+ - **Rotated fields** — Oracle `rotationAngle` (for example a sideways
255
+ window-envelope address) maps to SSRS `WritingMode` Rotate270.
256
+ - **Color resolution** — Oracle color names and indices are resolved to hex.
257
+
258
+ ### Sub-reports, drill-through, and envelopes
259
+
260
+ When the parser detects drill-through child reports, the Sub-Reports tab
261
+ generates an RDL for each child, declaring the parent's drill-through
262
+ parameters. Parameterized links to an envelope/cover report are emitted with
263
+ a deploy note, and rotated-field handling covers window-envelope address
264
+ blocks.
265
+
266
+ ### Bursting
267
+
268
+ Oracle *distribution* (one output file per permit / invoice / recipient,
269
+ written from one run) is detected from the source's own distribution
270
+ declarations and reproduced by a downloadable **Burst Pack**:
271
+
272
+ - the report's RDL gains ONE hidden parameter (`P_O2S_BURST_KEY`) and ONE
273
+ dataset filter -- set it and the report shows that key; leave it empty and
274
+ the report is exactly the one you would deploy anyway (SQL untouched);
275
+ - a companion `<Report>_BurstList.rdl` groups the same dataset by the key,
276
+ so the report server itself produces the key list (rendered as CSV);
277
+ - `Run-Burst.ps1` renders the report once per key through SSRS URL access
278
+ and saves each file under the Oracle file name (or emails it). Windows
279
+ PowerShell 5.1 built-ins only -- nothing is installed on any server, and
280
+ it can be tried from a desk PC with `-DryRun` / `-TestLimit 2` before a
281
+ service account and Task Scheduler take over.
282
+
283
+ SSRS Enterprise's native Data-Driven Subscriptions remain the alternative;
284
+ the pack's README gives the key-list SQL to paste into one.
285
+
286
+ ### Batch migration + Migration Assessment
287
+
288
+ Point the CLI at a whole folder of Oracle XML exports:
289
+
290
+ ```bash
291
+ python tools/batch_convert.py path/to/reports -o out --render
292
+ ```
293
+
294
+ Every report is converted, preflighted, fidelity-scored, optionally
295
+ render-verified through the MS engine, and classified into an effort tier
296
+ (`automatic` / `light-touch` / `assisted` / `manual`). You get
297
+ `out/rdl/*.rdl`, a printable `ASSESSMENT.html` executive summary, and a
298
+ `migration_pack.zip`. The same flow is available in the app sidebar ("Batch
299
+ migration") and over HTTP (`POST /api/batch`). The Community Edition
300
+ processes up to 10 reports per batch (single-report conversion is always
301
+ unlimited); set `O2S_LICENSE=pro` to lift the cap.
302
+
303
+ ### Zero-prompt parameters and data-source binding
304
+
305
+ - **Shared data source reference** — the RDL emits a
306
+ `<DataSourceReference>` to a named shared data source (with cached
307
+ credentials) rather than an embedded connection block. This is the
308
+ load-bearing design that stops the "Define Query Parameters" prompt at
309
+ Refresh Fields. Type your report server's shared data source path once in
310
+ the sidebar (for example `/Data Sources/MyOracle`) and every generated
311
+ artifact — main RDL, sub-report RDLs, the burst pack — ships pre-bound to
312
+ it.
313
+ - **Parameters never prompt end users** — undeclared query binds and
314
+ system/path parameters bind to an empty `=Nothing` default rather than
315
+ triggering a runtime prompt, and every query bind is wired, so the
316
+ report uploads and runs without ever asking for values. One honest
317
+ caveat: *manually executing* a parameterized dataset's query in the
318
+ designer (Refresh Fields / Query Designer ▸ Run) makes Report Builder
319
+ itself ask for design-time values — that is Report Builder's dialog for
320
+ any parameterized query, not an RDL defect, and it is entirely
321
+ avoidable because the field list is emitted complete: **never click
322
+ Refresh Fields**.
323
+ - **Hidden internal vs visible user params** — Oracle parameters marked
324
+ `display="no"`, and system/internal name patterns, become hidden
325
+ ReportParameters; genuine user parameters stay visible.
326
+ - **Dual SQL target** — the default (`oracle`) preserves the original
327
+ Oracle SQL with `:P_PARAM` binds and the OracleClient provider; the
328
+ `sqlserver` target emits translated T-SQL with `@P_PARAM` binds and the
329
+ SQL provider.
330
+
331
+ ### Heterogeneous ingest
332
+
333
+ Drop mixed artifacts and the ingester classifies each blob
334
+ (`primary_xml` / `rdf_binary` / `sql_files` / `pdfs` / `docs` /
335
+ `screenshots` / `unknown`) with a per-file confidence note, then runs
336
+ whatever subset of the pipeline is feasible:
337
+
338
+ - **`.rdf` onboarding** — `.rdf` is Oracle's proprietary binary and is not
339
+ parseable directly; the app replies with the exact one-line `rwconverter`
340
+ command (Oracle's own tool) to export it to the XML this converter
341
+ consumes, including the wildcard form for whole folders.
342
+ - **Raw `.sql` and `.docx`** — `.sql` files (and SQL extracted from `.docx`
343
+ walkthroughs) become synthetic datasets when no XML is present.
344
+ - **Partial artifacts** — incomplete exports (a customization overlay, a
345
+ data-model-only export, a layout fragment) get a plain-language verdict
346
+ instead of a near-blank RDL.
347
+ - **Encoding robustness** — best-effort decode tries utf-8 / utf-8-sig /
348
+ cp1252 / latin-1 before a replace fallback.
349
+
350
+ ### Pre-flight audit and cross-validation
351
+
352
+ The parser cross-checks bind variables, parameter names, and column
353
+ references against any `.sql`, `.docx`, or screenshot you dropped in, and
354
+ surfaces a pre-flight audit.
355
+
356
+ ### Bundle download
357
+
358
+ One click produces a `.zip` of the RDL, validation report, deploy checklist,
359
+ audit trail, burst pack, and a README explaining what's inside.
360
+
361
+ ## Manual deploy workflow
362
+
363
+ The converter intentionally stops at the file boundary — it does not push to
364
+ a Report Server. After downloading the `.rdl`:
365
+
366
+ 1. Upload it to your SSRS folder (Report Manager or Report Builder).
367
+ 2. Open the report's Data Source properties and point it at the shared data
368
+ source already configured in that folder.
369
+ 3. **Run.** Do not click Refresh Fields — the field list is emitted
370
+ complete and already matches the SQL aliases, so there is nothing to
371
+ refresh. (Refresh Fields executes the query at design time; on a
372
+ parameterized dataset Report Builder asks for design-time values —
373
+ its own dialog for any parameterized query. OK-with-nulls is safe if
374
+ you land there: the SELECT list is static.)
375
+ 4. Save, view, export to PDF for end users.
376
+
377
+ ## Architecture
378
+
379
+ Pipeline: parse the Oracle artifact into a single `ParsedReport` dataclass,
380
+ translate SQL and PL/SQL formula logic, generate the RDL, render previews,
381
+ validate, and build the deploy checklist and burst pack. Every module reads
382
+ or writes the same dataclass.
383
+
384
+ New here? Start with the **[deployment run guide](docs/RUN_GUIDE.md)** —
385
+ install, convert, read the verdict banner, and deploy to a Report Server.
386
+ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the module-by-module
387
+ breakdown and [docs/API.md](docs/API.md) for the HTTP endpoint reference.
388
+ Running it on an offline Windows Server? See the
389
+ [download list](docs/WINDOWS_SERVER_PREREQUISITES.md) and the
390
+ [Windows Server steps](docs/WINDOWS_SERVER_DEPLOYMENT.md).
391
+
392
+ ## Testing
393
+
394
+ ```bash
395
+ pip install -r requirements.txt
396
+ python -m pytest -q
397
+ ```
398
+
399
+ The suite is 791 tests. It validates generated RDL against Microsoft's RDL
400
+ 2008 XSD and, when the ReportViewer runtime is present, renders RDLs through
401
+ Microsoft's engine and measures the resulting PDFs.
402
+
403
+ ## Development
404
+
405
+ ```bash
406
+ python backend/cli.py samples/oracle --out ./out --strict
407
+ ```
408
+
409
+ `backend/cli.py` runs a plain conversion (`--out`, `--strict`, `--quiet`);
410
+ `tools/batch_convert.py` runs the batch Migration Assessment (`-o`,
411
+ `--render`, `--target-db`).
412
+
413
+ ## Contributing
414
+
415
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, test, and pull request
416
+ guidelines.
417
+
418
+ ## License
419
+
420
+ **MIT** — free to use, modify, distribute, and build on, for any purpose,
421
+ including commercial use. Full text in [LICENSE](LICENSE).
422
+
423
+ Optional paid tiers (Pro / Enterprise: unlimited batch, white-label
424
+ Migration Assessments, support) remain available — open an issue or contact
425
+ the author.