-
Notifications
You must be signed in to change notification settings - Fork 192
Node type change #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Node type change #87
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
5807659
functional import refactor - not tested, not used
tg44 05f2431
functional import refactor v2 - used instead of importer v1
tg44 18d2061
functional import refactor v3 - list fixes
tg44 17f1916
node type changes v1
tg44 0b342eb
rework node type change so we see the current doc as is
tg44 83fd946
track-changes - some list change imports
tg44 7fb388f
import nodes one-by-one and aggregate them in separate step
tg44 d0f5b9f
import cleanup
tg44 02eb6ca
nodetype changes - recreate old state
tg44 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
packages/super-editor/src/core/super-converter/v2/docxHelper.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| import xmljs from 'xml-js'; | ||
| /** | ||
| * @typedef {Object.<string, XmlNode>} ParsedDocx | ||
| * this should have the `media` as a key for image handling | ||
| */ | ||
|
|
||
| /** | ||
| * Convert docx to our inner representation | ||
| * @param {{name: string, content: string}[]} docxAsXmlFileList | ||
| * @returns {ParsedDocx} | ||
| */ | ||
| export const docxAsXmlFileList2ParsedDocx = (docxAsXmlFileList) => { | ||
| const convertedXml = {} | ||
| docxAsXmlFileList.forEach(file => { | ||
| convertedXml[file.name] = parseXmlToJson(file.content); | ||
| }); | ||
| return convertedXml; | ||
| } | ||
|
|
||
| const defaultInitialXml = `<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14"><w:body></w:body></w:document>` | ||
|
|
||
| export const parseXmlToJson = (xml) => { | ||
| return JSON.parse(xmljs.xml2json(xml, null, 2)) | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param {ParsedDocx} parsedDocx | ||
| * @param {string} [fallbackXml] | ||
| */ | ||
| export const getInitialJSON = (parsedDocx, fallbackXml = defaultInitialXml) => { | ||
| return parsedDocx['word/document.xml'] || parseXmlToJson(fallbackXml); | ||
| } | ||
| /** | ||
| * | ||
| * @param {ParsedDocx} parsedDocx | ||
| * @returns * | ||
| */ | ||
| const getDeclaration = (parsedDocx) => { | ||
| return getInitialJSON(parsedDocx).declaration; | ||
| } | ||
|
|
||
| const getThemeInfo = (themeName) =>{ | ||
| themeName = themeName.toLowerCase(); | ||
| const theme1 = this.convertedXml['word/theme/theme1.xml']; | ||
| const themeData = theme1.elements.find((el) => el.name === 'a:theme'); | ||
| const themeElements = themeData.elements.find((el) => el.name === "a:themeElements"); | ||
| const fontScheme = themeElements.elements.find((el) => el.name === 'a:fontScheme'); | ||
| let fonts; | ||
|
|
||
| if (themeName.startsWith('major')) { | ||
| fonts = fontScheme.elements.find((el) => el.name === 'a:majorFont').elements[0]; | ||
| } else if (themeName.startsWith('minor')) { | ||
| fonts = fontScheme.elements.find((el) => el.name === 'a:minorFont').elements[0]; | ||
| } | ||
|
|
||
| const { typeface, panose } = fonts.attributes; | ||
| return { typeface, panose }; | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param {ParsedDocx} parsedDocx | ||
| * @returns {{} | {fontSizePt: number, kern: string, typeface: string, panose: string}} | ||
| */ | ||
| const getDocumentDefaultStyles = (parsedDocx) => { | ||
| const styles = parsedDocx['word/styles.xml']; | ||
| if (!styles) return {}; | ||
|
|
||
| const defaults = styles.elements[0].elements.find((el) => el.name === 'w:docDefaults'); | ||
|
|
||
| // TODO: Check if we need this | ||
| // const pDefault = defaults.elements.find((el) => el.name === 'w:pPrDefault'); | ||
|
|
||
| // Get the run defaults for this document - this will include font, theme etc. | ||
| const rDefault = defaults.elements.find((el) => el.name === 'w:rPrDefault'); | ||
| if ('elements' in rDefault) { | ||
| const rElements = rDefault.elements[0].elements | ||
| const fontThemeName = rElements.find((el) => el.name === 'w:rFonts')?.attributes['w:asciiTheme']; | ||
| let typeface, panose; | ||
| if (fontThemeName) { | ||
| const fontInfo = getThemeInfo(fontThemeName); | ||
| typeface = fontInfo.typeface; | ||
| panose = fontInfo.panose; | ||
| } | ||
|
|
||
| const fontSizePt = Number(rElements.find((el) => el.name === 'w:sz')?.attributes['w:val']) / 2; | ||
| const kern = rElements.find((el) => el.name === 'w:kern')?.attributes['w:val']; | ||
| return { fontSizePt, kern, typeface, panose }; | ||
| } | ||
| return {}; | ||
| } |
28 changes: 28 additions & 0 deletions
28
packages/super-editor/src/core/super-converter/v2/importer/bookmarkNodeImporter.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| /** | ||
| * @type {import("docxImporter").NodeHandler} | ||
| */ | ||
| export const handleBookmarkNode = (nodes, docx, nodeListHandler, insideTrackChange) => { | ||
| if(nodes.length === 0 || nodes[0].name !== 'w:bookmarkStart') { | ||
| return []; | ||
| } | ||
| const node = nodes[0]; | ||
|
|
||
| const handleStandardNode = nodeListHandler.handlerEntities.find(e => e.handlerName === 'standardNodeHandler')?.handler; | ||
| if (!handleStandardNode) { | ||
| console.error('Standard node handler not found'); | ||
| return []; | ||
| } | ||
| const result = handleStandardNode([node], docx, nodeListHandler, insideTrackChange); | ||
| if(result.length === 1) { | ||
| result[0].attrs.name = node.attributes['w:name']; | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * @type {import("docxImporter").NodeHandlerEntry} | ||
| */ | ||
| export const bookmarkNodeHandlerEntity = { | ||
| handlerName: 'bookmarkNodeHandler', | ||
| handler: handleBookmarkNode | ||
| }; |
30 changes: 30 additions & 0 deletions
30
packages/super-editor/src/core/super-converter/v2/importer/boomarkNodeImporter.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import {SuperConverter} from "../../SuperConverter.js"; | ||
| import {handleBookmarkNode} from "./bookmarkNodeImporter.js"; | ||
| import {createNodeListHandlerMock} from "./test-helpers/testUtils.test.js"; | ||
|
|
||
|
|
||
| describe('BookmarkNodeImporter', () => { | ||
| it("parses only bookmark nodes", () => { | ||
| const names = Object.keys(SuperConverter.allowedElements).filter((name) => name !== 'w:bookmarkStart'); | ||
| const nodesOfNodes = names.map((name) => ([{name}])); | ||
| for(const nodes of nodesOfNodes) { | ||
| const result = handleBookmarkNode(nodes, null, null, false); | ||
| expect(result.length).toBe(0); | ||
| } | ||
| }) | ||
| it("parses bookmark nodes and w:name attributes", () => { | ||
| const nodes = [{name: 'w:bookmarkStart', attributes: {'w:name': 'bookmarkName'}}]; | ||
| const result = handleBookmarkNode(nodes, null, createNodeListHandlerMock(), false); | ||
| expect(result.length).toBe(1); | ||
| expect(result[0].type).toBe('standardNodeHandler'); | ||
| expect(result[0].attrs.name).toBe("bookmarkName"); | ||
| }) | ||
| it("parser relies on handleStandardNode", () => { | ||
| const consoleMock = vi.spyOn(console, 'error').mockImplementation(() => undefined); | ||
|
|
||
| const nodes = [{name: 'w:bookmarkStart', attributes: {'w:name': 'bookmarkName'}}]; | ||
| const result = handleBookmarkNode(nodes, null, {handlerEntities: []}, false); | ||
| expect(result.length).toBe(0); | ||
| expect(consoleMock).toHaveBeenCalledOnce(); | ||
| }) | ||
| }) |
181 changes: 181 additions & 0 deletions
181
packages/super-editor/src/core/super-converter/v2/importer/docxImporter.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| import {getInitialJSON} from "../docxHelper.js"; | ||
| import {carbonCopy} from "../../../utilities/cabonCopy.js"; | ||
| import {twipsToInches} from "../../helpers.js"; | ||
| import {tableNodeHandlerEntity} from "./tableImporter.js"; | ||
| import {drawingNodeHandlerEntity} from "./imageImporter.js"; | ||
| import { | ||
| handleParagraphNodeChange, | ||
| nodeTypeChangeAttributeChanger, | ||
| trackChangeNodeHandlerEntity | ||
| } from "./trackChangesImporter.js"; | ||
| import {hyperlinkNodeHandlerEntity} from "./hyperlinkImporter.js"; | ||
| import {runNodeHandlerEntity} from "./runNodeImporter.js"; | ||
| import {textNodeHandlerEntity} from "./textNodeImporter.js"; | ||
| import {paragraphNodeHandlerEntity} from "./paragraphNodeImporter.js"; | ||
| import {standardNodeHandlerEntity} from "./standardNodeImporter.js"; | ||
| import {lineBreakNodeHandlerEntity} from "./lineBreakImporter.js"; | ||
| import {bookmarkNodeHandlerEntity} from "./bookmarkNodeImporter.js"; | ||
| import {listHandlerEntity, listNodeAggregator} from "./listImporter.js"; | ||
|
|
||
| /** | ||
| * @typedef {import()} XmlNode | ||
| * @typedef {{type: string, content: *, attrs: {}}} PmNodeJson | ||
| * @typedef {{type: string, attrs: {}}} PmMarkJson | ||
| * | ||
| * @typedef {(nodes: XmlNode[], docx: ParsedDocx, insideTrackCahange: boolean) => PmNodeJson[]} NodeListHandlerFn | ||
| * @typedef {{handler: NodeListHandlerFn, handlerEntities: NodeHandlerEntry[]}} NodeListHandler | ||
| * | ||
| * @typedef {(nodes: XmlNode[], docx: ParsedDocx, nodeListHandler: NodeListHandler, insideTrackCahange: boolean) => PmNodeJson[]} NodeHandler | ||
| * @typedef {{handlerName: string, handler: NodeHandler}} NodeHandlerEntry | ||
| */ | ||
|
|
||
| /** | ||
| * | ||
| * @param {ParsedDocx} docx | ||
| * @returns {{pmDoc: PmNodeJson, savedTagsToRestore: XmlNode, pageStyles: *}|null} | ||
| */ | ||
| export const createDocumentJson = (docx) => { | ||
| const json = carbonCopy(getInitialJSON(docx)); | ||
| if (!json) return null; | ||
|
|
||
| console.debug('\n\n JSON', json,) | ||
| const nodeListHandler = defaultNodeListHandler(); | ||
| if(json.elements[0].elements[0].name === 'w:body') { | ||
| const node = json.elements[0].elements[0]; | ||
| const ignoreNodes = ['w:sectPr']; | ||
| const content = node.elements?.filter((n) => !ignoreNodes.includes(n.name)) ?? []; | ||
|
|
||
| const result = { | ||
| type: 'doc', | ||
| content: nodeListHandler.handler(content, docx, false), | ||
| attrs: { | ||
| attributes: json.elements[0].attributes, | ||
| } | ||
| } | ||
| return { | ||
| pmDoc: result, | ||
| savedTagsToRestore: node, | ||
| pageStyles: getDocumentStyles(node), | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * @returns {NodeListHandler} | ||
| */ | ||
| export const defaultNodeListHandler = () => { | ||
| const entities = [ | ||
| runNodeHandlerEntity, | ||
| paragraphNodeHandlerEntity, | ||
| listHandlerEntity, | ||
| textNodeHandlerEntity, | ||
| lineBreakNodeHandlerEntity, | ||
| bookmarkNodeHandlerEntity, | ||
| hyperlinkNodeHandlerEntity, | ||
| drawingNodeHandlerEntity, | ||
| trackChangeNodeHandlerEntity, | ||
| tableNodeHandlerEntity, | ||
| standardNodeHandlerEntity, //this should be the last one, bcs this parses everything!!! | ||
| ] | ||
| const handler = createNodeListHandler(entities); | ||
| return { | ||
| handler, | ||
| handlerEntities: entities | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param {NodeHandlerEntry[]} nodeHandlers | ||
| */ | ||
| const createNodeListHandler = (nodeHandlers) => { | ||
| /** | ||
| * @param {XmlNode[]} elements | ||
| * @param {ParsedDocx} docx | ||
| * @param {boolean} insideTrackChange | ||
| * @return {{type: string, content: *, attrs: {attributes}}[]} | ||
| */ | ||
| const nodeListHandlerFn = (elements, docx, insideTrackChange) => { | ||
| if (!elements || !elements.length) return []; | ||
|
|
||
| const handlerObj = {handler: nodeListHandlerFn, handlerEntities: nodeHandlers} | ||
| const processedElements = []; | ||
|
|
||
| for (let index = 0; index < elements.length; index++) { | ||
| const nodes = nodeHandlers.reduce((res, handler) => { | ||
| if(res.length > 0) return res; | ||
| const nodesToHandle = elements.slice(index); | ||
| if(!nodesToHandle || nodesToHandle.length === 0) return res; | ||
| const handlerResponse = handler.handler(nodesToHandle, docx, handlerObj, insideTrackChange); | ||
| if(handlerResponse.length > 0) { | ||
| handleParagraphNodeChange(nodesToHandle[0], handlerResponse[0], docx, handlerObj) | ||
| return handlerResponse; | ||
| } | ||
| return res | ||
| }, []); | ||
| if(nodes.length === 0) { | ||
| console.warn("We have a node that we can't handle!", elements[index]) | ||
| } | ||
| for(let node of nodes) { | ||
| if (node?.type) { | ||
| const ignore = ['runProperties']; | ||
| if (!ignore.includes(node.type)) processedElements.push(node); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const aggregatedNodes = listNodeAggregator(processedElements, docx, handlerObj); | ||
|
|
||
| return aggregatedNodes; | ||
| } | ||
|
|
||
| return nodeListHandlerFn; | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param {XmlNode} node | ||
| * @returns {*} | ||
| */ | ||
| function getDocumentStyles(node) { | ||
| const sectPr = node.elements.find((n) => n.name === 'w:sectPr'); | ||
| const styles = {}; | ||
|
|
||
| sectPr.elements.forEach((el) => { | ||
| const { name, attributes } = el; | ||
| switch (name) { | ||
| case 'w:pgSz': | ||
| styles['pageSize'] = { | ||
| width: twipsToInches(attributes['w:w']), | ||
| height: twipsToInches(attributes['w:h']), | ||
| } | ||
| break; | ||
| case 'w:pgMar': | ||
| styles['pageMargins'] = { | ||
| top: twipsToInches(attributes['w:top']), | ||
| right: twipsToInches(attributes['w:right']), | ||
| bottom: twipsToInches(attributes['w:bottom']), | ||
| left: twipsToInches(attributes['w:left']), | ||
| header: twipsToInches(attributes['w:header']), | ||
| footer: twipsToInches(attributes['w:footer']), | ||
| gutter: twipsToInches(attributes['w:gutter']), | ||
| } | ||
| break; | ||
| case 'w:cols': | ||
| styles['columns'] = { | ||
| space: twipsToInches(attributes['w:space']), | ||
| num: attributes['w:num'], | ||
| equalWidth: attributes['w:equalWidth'], | ||
| } | ||
| break; | ||
| case 'w:docGrid': | ||
| styles['docGrid'] = { | ||
| linePitch: twipsToInches(attributes['w:linePitch']), | ||
| type: attributes['w:type'], | ||
| } | ||
| break; | ||
| } | ||
| }); | ||
| return styles; | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be refactored to
node: XmlNodebcs we removed then to nmapping, and we did an1 to nwhich is way easier to think about. (This is really just refactoring the array to a single element.)